How to get multiple sprites to bounce off the bounds of a scene in Objective-C - objective-c

I have a game I'm creating with sprite-kit with 25 sprites that are all children of one sprite node BOMNeutNode. At the beginning of the game I create 25 child nodes within the scene using a for-loop, and set them all moving in a random direction using physicsBody.velocity.
What I want to do is to get them to bounce off the bounds of the scene when they reach the edge. I assumed the code below would do the trick to begin with, but it only seems to work on 1 of the 25 nodes (for that one node it is working perfectly well). I'm thinking this must be due to where I have this code positioned in my GamePlayScene code. I thought it would be appropriate to put it in the update section of the game run loop. Maybe I have to set this rule in the node itself?
I also thought it might be that I need to identify ALL nodes named #"Neut" but I can't find the syntax to do this. Please let me know if you need more code than I have provided.
(void) update:(NSTimeInterval)currentTime {
BOMNeutNode *neut = (BOMNeutNode*)[self childNodeWithName:#"Neut"];
if (neut.position.y > self.frame.size.height-50) {
neut.physicsBody.velocity = CGVectorMake(0, -100);
} else if (neut.position.y < 50) {
neut.physicsBody.velocity = CGVectorMake(0, 100);
} else if (neut.position.x > self.frame.size.height-50) {
neut.physicsBody.velocity = CGVectorMake(-100, 0);
} else if (neut.position.x < 50) {
neut.physicsBody.velocity = CGVectorMake(100, 0);
}
}
Any suggestions would be greatly appreciated.

One easy way to do this is to add a SKPhysicsBody created with bodyWithEdgeLoopFromRect: to your SKScene. That way the physics engine can handle all the bouncing for you (with the correct angles and everything).
Typically you'd want to add it in your SKScene's didMoveToView: method. Something like:
- (void)didMoveToView:(SKView*)view {
// ... any other setup you might need ...
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(50, 50, self.size.width - 100, self.size.hight - 100)];
}
If you go this route, that should take care of everything; you shouldn't need the code you currently have in your update: method.

The problem is that you have many nodes called Neut and its finding one of them and checking if its inside the frame, what you need to do is to find all nodes with that name and run the code on all of them
-(void)update:(NSTimeInterval)currentTime {
[self enumerateChildNodesWithName:#"Neut" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y > self.frame.size.height-50) {
node.physicsBody.velocity = CGVectorMake(0, -100);
} else if (node.position.y < 50) {
node.physicsBody.velocity = CGVectorMake(0, 100);
} else if (node.position.x > self.frame.size.height-50) {
node.physicsBody.velocity = CGVectorMake(-100, 0);
} else if (node.position.x < 50) {
node.physicsBody.velocity = CGVectorMake(100, 0);
}
}];
}

Related

Best way to do object collision?

I'm trying to do wall collision for objects and I've followed a tutorial that offers one method of doing collision.
This is the tutorial: https://www.youtube.com/watch?v=yZU1QJJdxgs
Currently, if the object detects a wall, instead of moving it's full distance, it moves pixel by pixel until it's against the wall. This worked well until I started trying to rotate the object with image_rotate, because it caused objects to get stuck in walls by either sliding against them or if they rotated into them.
I fixed this by using draw_sprite_ext instead and changing the rotation of the sprite itself and not the mask, which worked for about 20 minutes until it started causing more problems.
///obj_player Step
//Initialise Variables
hor_speed = 0;
ver_speed = 0;
accelerationspeed = 0.2;
decelerationspeed = 0.2;
maxspeed = 3;
pointdirection = 0;
//Get player's input
key_right = keyboard_check(ord("D"))
key_left = -keyboard_check(ord("A"))
key_up = -keyboard_check(ord("W"))
key_down = keyboard_check(ord("S"))
pointdirection = point_direction(x,y,mouse_x,mouse_y) + 270
hor_movement = key_left + key_right;
ver_movement = key_up + key_down;
//horizontal acceleration
if !(abs(hor_speed) >= maxspeed) {
hor_speed += hor_movement * accelerationspeed;
}
//horizontal deceleration
if (hor_movement = 0) {
if !(hor_speed = 0) {
hor_speed -= (sign(hor_speed) * decelerationspeed)
}
}
//vertical acceleration
if !(abs(ver_speed) >= maxspeed) {
ver_speed += ver_movement * accelerationspeed;
}
//vertical deceleration
if (ver_movement = 0) {
if !(ver_speed = 0) {
ver_speed -= (sign(ver_speed) * decelerationspeed)
}
}
//horizontal collision
if (place_meeting(x+hor_speed,y,obj_wall)) {
while(!place_meeting(x+sign(hor_speed),y,obj_wall)) {
x += sign(hor_speed);
}
hor_speed = 0;
}
//vertical collision
if (place_meeting(x,y+ver_speed,obj_wall)) {
while(!place_meeting(x,y+sign(ver_speed),obj_wall)) {
y += sign(ver_speed);
}
ver_speed = 0;
}
//move the player
x += hor_speed;
y += ver_speed;
///obj_player Draw
//rotate to look at cursor
draw_sprite_ext(spr_player, 0, x,y,image_xscale,image_yscale, pointdirection, image_blend, image_alpha);
I think the best way to rotate objects is through image_rotate, and I'd like to do it without getting stuff stuck in walls. Can my current method of collision be adapted to do this, or should I attempt to do it in a different way?
Your code looks fine, but if you're going to be rotating objects then you would also need to consider having a "knock back mechanic." Reason being is the player could be sitting next to this wall and if you rotate the object over them so they cant move, its not a fun time being stuck.
So you 'could' have the object that's rotating do a check before rotating and if objects are in the way then either stop it or push them back so they cant be within range.

Pick a next item from NSArray and then start over

I need to toggle three different font sizes in the view controller for terms and conditions screen in an endless loop (13 , 15, 17..13, 15, 17..etc..). The screen is pretty simple, just text on full screen and a button in the navigation bar that when pressed, triggers and event handled in action.
The three fonts are represented by three NSString constants.
-(IBAction)toggleFontSize:(id)sender
{
if (self.currentFontIdentifier == regularFontIdentifier)
{
self.currentFontIdentifier = largeFontIdentifier;
}
else if (self.currentFontIdentifier == largeFontIdentifier)
{
self.currentFontIdentifier = smallFontIdentifier;
}
else
{
self.currentFontIdentifier = regularFontIdentifier;
}
self.termsAndConditionsTextView.font = [[BrandingManager sharedManager] fontWithIdentifier:self.currentFontIdentifier];
}
This code works (for now :)), but it's a nice Mediterranean IF yacht.I am wondering if there is some more mature approach. I already see the stakeholders changing their mind and adding a 4th font size. I want it to be manageable better, so basically once they add a new size I would only add it into some Array and that would be it.
Any ideas for a more mature algorithm?
Declare an instance variable for the current selected index and an array for the three fonts (small, regular and large) and try this:
-(IBAction)toggleFontSize:(id)sender {
_currentSelectedIndex = (_currentSelectedIndex + 1) % 3;
self.currentFontIdentifier = _fontIdentifiers[_currentSelectedIndex];
self.termsAndConditionsTextView.font = [[BrandingManager sharedManager] fontWithIdentifier:self.currentFontIdentifier];
}
You may not need currentFontIdentifier property since it can be obtained with _fontIdentifiers[_currentSelectedIndex]
You could use the methods 'indexOfObject and 'lastObject' of the NSArray class, something like:
Using an array of sizes:
NSArray *fontList = #[#"12","14","18"];
Then you could iterate through it using the indexOfObject
NSUInteger ix = [fontList indexOfObject:self.currentFontIdentifier] + 1;
if ([[fontList lastObject] isEqual:self.currentFontIdentifier])
ix=0;
self.currentFontIdentifier = [fontList objectAtIndex:ix];
or
NSUInteger ix = [fontList indexOfObject:self.currentFontIdentifier] + 1;
if (ix >= [fontList count])
ix=0;
self.currentFontIdentifier = [fontList objectAtIndex:ix];

Rendering QCRenderer for displaying onto DeckLink Cards

I'm writing an application in xcode on a MacMini (Late 2012).
It's an app where I load some QuartzComposer files (with QCRenderer class). Then render those files into videomemory and read them back using glReadPixels to get all the pixel data. This pixel data is then pushed to a decklink frame (I'm using BlackMagic Decklink SDK) for playback onto a DeckLink Quad. Everything is working great. It's even possible to render 3 outputs without dropping frames on HD (1080i50). But after a while (like 5 mins) it is dropping frames even when I'm only rendering 1 output.
So I think there are 2 possible reasons. First. When there is a completed frame (frame dit played out) callback I'm receiving the bmdOutputFrameDisplayedLate from the SDK which means the frame was not played at the time it was scheduled for. So when this happens I'm pushing the next frame by 1 into the future.
Second. I've set a frameBuffer Size (3 frames are rendered out before playback will be started). So maybe after a while rendering is falling behind the scheduling which will cause the dropping/late frames. Maybe i'm not doing the openGL rendering process like it should be?
So here's my code:
-> first I'm loading a QCRenderer into memory
- (id)initWithPath:(NSString*)path forResolution:(NSSize)resolution
{
if (self = [super init])
{
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFAPixelBuffer,
NSOpenGLPFANoRecovery,
NSOpenGLPFAAccelerated,
NSOpenGLPFADepthSize, 24,
(NSOpenGLPixelFormatAttribute) 0
};
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
quartzPixelBuffer = nil;
quartzPixelBuffer = [[NSOpenGLPixelBuffer alloc] initWithTextureTarget:GL_TEXTURE_2D textureInternalFormat:GL_RGBA textureMaxMipMapLevel:0 pixelsWide:resolution.width pixelsHigh:resolution.height];
if(quartzPixelBuffer == nil)
{
NSLog(#"Cannot create OpenGL pixel buffer");
}
//Create the OpenGL context to render with (with color and depth buffers)
quartzOpenGLContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil];
if(quartzOpenGLContext == nil)
{
NSLog(#"Cannot create OpenGL context");
}
[quartzOpenGLContext setPixelBuffer:quartzPixelBuffer cubeMapFace:0 mipMapLevel:0 currentVirtualScreen:[quartzOpenGLContext currentVirtualScreen]];
//Create the QuartzComposer Renderer with that OpenGL context and the specified composition file
NSString* correctPath = [path substringWithRange:NSMakeRange(0, path.length - 1)];
quartzRenderer = [[QCRenderer alloc] initWithOpenGLContext:quartzOpenGLContext pixelFormat:format file:correctPath];
if(quartzRenderer == nil)
{
NSLog(#"Cannot create QCRenderer");
}
}
return self;
}
-> next step is to render 3 frames (BUFFER_DEPTH is set to 3 currently) before starting playback
- (void) preRollFrames;
{
// reset scheduled
[self resetScheduled];
totalFramesScheduled = 0;
if (isRunning == TRUE)
{
[self stopPlayback];
}
#autoreleasepool
{
for (double i = 0.0; i < ((1.0 / framesPerSecond) * BUFFER_DEPTH); i += 1.0/framesPerSecond)
{
// render image at given time
[self createVideoFrame:TRUE];
}
}
}
-> this is the createVideoFrame function. When scheduleBlack is set to true there must be rendered a black frame. If false the function renderFrameAtTime for the QCRenderer class is called. The return of this function is then passed to the decklinkVideoFrame object. Next, this frame will be pushed into the schedule queue of the DeckLink Card (SDK).
- (void) createVideoFrame:(BOOL)schedule
{
#autoreleasepool
{
// get displaymode
IDeckLinkDisplayMode* decklinkdisplaymode = (IDeckLinkDisplayMode*)CurrentRes;
// create new videoframe on output
if (deckLinkOutput->CreateVideoFrame((int)decklinkdisplaymode->GetWidth(), (int)decklinkdisplaymode->GetHeight(), (int)decklinkdisplaymode->GetWidth() * 4, bmdFormat8BitARGB, bmdFrameFlagFlipVertical, &videoFrame) != S_OK)
{
// failed to create new video frame on output
// display terminal message
sendMessageToTerminal = [[mogiTerminalMessage alloc] initWithSendNotification:#"terminalErrorMessage" forMessage:[NSString stringWithFormat:#"DeckLink: Output %d -> Failed to create new videoframe", outputID]];
}
unsigned frameBufferRowBytes = ((int)decklinkdisplaymode->GetWidth() * 4 + 63) & ~63;
void* frameBufferPtr = valloc((int)decklinkdisplaymode->GetHeight() * frameBufferRowBytes);
// set videoframe pointer
if (videoFrame != NULL)
{
videoFrame->GetBytes((void**)&frameBufferPtr);
}
// fill pointer with pixel data
if (scheduleBlack == TRUE)
{
[qClear renderFrameAtTime:1.0 forBuffer:(void**)frameBufferPtr forScreen:0];
// render first frame qRenderer
if (qRender != NULL)
{
[qRender renderFirstFrame];
}
}
else
{
[qRender renderFrameAtTime:totalSecondsScheduled forBuffer:(void**)frameBufferPtr forScreen:screenID];
schedule = TRUE;
}
// if playback -> schedule frame
if (schedule == TRUE)
{
// schedule frame
if (videoFrame != NULL)
{
if (deckLinkOutput->ScheduleVideoFrame(videoFrame, (totalFramesScheduled * frameDuration), frameDuration, frameTimescale) != S_OK)
{
// failed to schedule new frame
// display message to terminal
sendMessageToTerminal = [[mogiTerminalMessage alloc] initWithSendNotification:#"terminalErrorMessage" forMessage:[NSString stringWithFormat:#"DeckLink: Output %d -> Failed to schedule new videoframe", outputID]];
}
else
{
// increase totalFramesScheduled
totalFramesScheduled ++;
// increase totalSecondsScheduled
totalSecondsScheduled += 1.0/framesPerSecond;
}
// clear videoframe
videoFrame->Release();
videoFrame = NULL;
}
}
}
}
-> render frameAtTime function from QCRenderer class
- (void) renderFrameAtTime:(double)time forBuffer:(void*)frameBuffer forScreen:(int)screen
{
#autoreleasepool
{
CGLContextObj cgl_ctx = [quartzOpenGLContext CGLContextObj];
// render frame at time
[quartzRenderer renderAtTime:time arguments:NULL];
glReadPixels(0, 0, [quartzPixelBuffer pixelsWide], [quartzPixelBuffer pixelsHigh], GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, frameBuffer);
}
}
-> After perolling frames Playback is started. Each time there's a frame played out. This Callback method is called (Decklink SDK). If there's a late frame. I'm pushing the totalFrames 1 frame into future.
PlaybackDelegate::PlaybackDelegate (DecklinkDevice* owner)
{
pDecklinkDevice = owner;
}
HRESULT PlaybackDelegate::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
{
if (result == bmdOutputFrameDisplayedLate)
{
// if displayed late bump scheduled time further into the future by one frame
[pDecklinkDevice increaseScheduledFrames];
NSLog(#"bumped %d", [pDecklinkDevice getOutputID]);
}
if ([pDecklinkDevice getIsRunning] == TRUE)
{
[pDecklinkDevice createVideoFrame:TRUE];
}
return S_OK;
}
So my question. Am I doing the openGL rendering process correct? Maybe that is causing the delay after some minutes. Or I'm a handling the displayedLate frame incorrect so the timing of the scheduling queue is messed up after some time?
Thx!
Thomas
When late, try advancing the frame counter according to the completion result specified by the ScheduledFrameCompleted callback. Consider an extra increment of two.
At least on Windows and for many years now, only workstation boards provide unthrottled pixel readback when using NVidia's products. My iMac has a GeForce series card but I haven't measured its performance. I wouldn't be surprised if glReadPixels is throttled.
Also try using GL_BGRA_EXT and GL_UNSIGNED_INT_8_8_8_8_REV.
You should have precision timing metrics for glReadPixels and the writes to hardware. I assume you're reading back progressive or interlaced frames but not fields. Ideally, pixel readback should be less than 10 ms. And obviously, the entire render cycle needs be faster than the video hardware's frame rate.

Actionscript 3: How to make movieclip variables work with variables on stage (Healthbar Help)

I am totally new at this whole programming thing, and I really need help. So basically, I'm trying to make a healthbar that will increase or decrease depending on what button is clicked. I made a healthbar movieclip with 101 frames (I included zero) and put this in the actionscript layer of the movieclip:
var health:Number = 0;
if(health == 0)
{
gotoAndStop("1")
}
if(health == 1)
{
gotoAndStop("2")
}
if(health == 2)
{
gotoAndStop("3")
}
and on and on like so. Basically, on the stage itself, I have a button called fortyfiveup_btn that is commanded to do this:
var health:Number = 0;
fortyfiveup_btn.addEventListener(MouseEvent.CLICK, fortyfiveupClick);
function fortyfiveupClick(event:MouseEvent):void{
health = health+45
}
I quickly realized that both health variables, the one for the button and the one for the healthbar will not interact. How can I make it so if the button is clicked, the health goes to the relevant frame or percentage?
Thanks for any answers, and I appreciate all the help I can get :)
If the answer == yes to my comment you should do this:
You need to give the movieclip an instancename (perhaps lifebar) and from stage you can access the health inside the "lifebar" with lifebar.health.
So you need this inside your stage:
//You can delete the var health:Number = 0;
fortyfiveup_btn.addEventListener(MouseEvent.CLICK, fortyfiveupClick);
function fortyfiveupClick(event:MouseEvent):void{
//You can write this, too: lifebar.health += 45;
lifebar.health = lifebar.health+45;
}
You can even optimize your lifebar script, don't use 101 times the if(health == x) you can use this, too:
gotoAndStop(health + 1);
(I think this is inside an ENTER_FRAME event?)
EDIT:
Some error countermeasures:
//Don't let health decrease below 0
if(health < 0) health = 0;
//And don't above 100
else if(health > 100) health = 100;
gotoAndStop(health + 1);
Use int instead of Number when you don't use decimal numbers and uint when you don't use negative integers (this bugs when the number can drop under 0, so for your health we take int):
health:int = 0;

point light illumination using Phong model

I wish to render a scene that contains one box and a point light source using the Phong illumination scheme. The following are the relevant code snippets for my calculation:
R3Rgb Phong(R3Scene *scene, R3Ray *ray, R3Intersection *intersection)
{
R3Rgb radiance;
if(intersection->hit == 0)
{
radiance = scene->background;
return radiance;
}
...
// obtain ambient term
... // this is zero for my test
// obtain emissive term
... // this is also zero for my test
// for each light in the scene, obtain calculate the diffuse and specular terms
R3Rgb intensity_diffuse(0,0,0,1);
R3Rgb intensity_specular(0,0,0,1);
for(unsigned int i = 0; i < scene->lights.size(); i++)
{
R3Light *light = scene->Light(i);
R3Rgb light_color = LightIntensity(scene->Light(i), intersection->position);
R3Vector light_vector = -LightDirection(scene->Light(i), intersection->position);
// check if the light is "behind" the surface normal
if(normal.Dot(light_vector)<=0)
continue;
// calculate diffuse reflection
if(!Kd.IsBlack())
intensity_diffuse += Kd*normal.Dot(light_vector)*light_color;
if(Ks.IsBlack())
continue;
// calculate specular reflection
... // this I believe to be irrelevant for the particular test I'm doing
}
radiance = intensity_diffuse;
return radiance;
}
R3Rgb LightIntensity(R3Light *light, R3Point position)
{
R3Rgb light_intensity;
double distance;
double denominator;
if(light->type != R3_DIRECTIONAL_LIGHT)
{
distance = (position-light->position).Length();
denominator = light->constant_attenuation +
(light->linear_attenuation*distance) +
(light->quadratic_attenuation*distance*distance);
}
switch(light->type)
{
...
case R3_POINT_LIGHT:
light_intensity = light->color/denominator;
break;
...
}
return light_intensity;
}
R3Vector LightDirection(R3Light *light, R3Point position)
{
R3Vector light_direction;
switch(light->type)
{
...
case R3_POINT_LIGHT:
light_direction = position - light->position;
break;
...
}
light_direction.Normalize();
return light_direction;
}
I believe that the error must be somewhere in either LightDirection(...) or LightIntensity(...) functions because when I run my code using a directional light source, I obtain the desired rendered image (thus this leads me to believe that the Phong illumination equation is correct). Also, in Phong(...), when I computed the intensity_diffuse and while debugging, I divided light_color by 10, I was obtaining a resulting image that looked more like what I need. Am I calculating the light_color correctly?
Thanks.
Turned out I had no error. The "final image" I was comparing my results to wasn't computed correctly.