Stopping physics body from movement in Corona SDK - mobile

I'm learning Corona SDK and I'm making small project in that purpose.
So, my problem is next one:
I managed to create 2 physics objects and make one of them "explode" when it collides with the other one. My question is how to make other object (it has linear impulse applied) stop when it collides? Also, when it stops, it has to be removed from the screen to avoid colliding with other objects...
Here is a part with removing first object on collision:
nloDrop = function()
local nlo = display.newImageRect("nlo.png", 65, 25)
nlo.x = 35 + mRand(410) ; nlo.y = -60
physics.addBody(nlo, "dynamic", {density=1, bounce = 0, friction = 0, filter = {maskBits = 4, categoryBits = 2}})
nlo:applyLinearImpulse(0, 0.8, nlo.x, nlo.y)
nlo.isSensor = true
nlo.collision = nloCollision
nlo:addEventListener("collision", nlo)
nlo.name = "nlo"
toFront()
end
And here is 'collision' function:
function nloCollision(self, event)
if ((event.other.myName == "weaponName") then
print("funkc")
self:removeSelf()
self:removeEventListener("collision", nlo)
self = nil
if weapon ~= nil then
-- stop moving of weapon
end
end
end
Thanks!

You can make the object bodyActive false and then it will not respond to physics. You cant remove a body from physics within the active screen so its a better option to keep that object out of the screen.

I made it setting the object like local variable and making a function that deletes/removes each variable (object) after some interaction or collision.
1st function contains object creating (that is a local type under function) and applying physics to that object.
2nd function contains deleting (self:removeSelf()) that works because each object is object for itself and when deleting it, physics stuff will continue to work because new local object is going to be created.
function create(event)
local weap1 = display.newImage("weap1.png", 0, 0)
weap1.x = turret.x ; weap1.y = turret.y
weap1.rotation = turret.rotation
weap1.collision = weap1Collision
weap1:addEventListener("collision", weap1)
physics.addBody(weap1, "dynamic", {density = 1, friction = 0, bounce = 0, filter = {maskBits = 2, categoryBits = 4}})
weap1:applyLinearImpulse(forceWeap1*xComp, forceWeap1*yComp, weap1.x, weap1.y)
function weap1Collision(self,event)
if (event.other.name == "collisionObject") then
self:removeSelf()
self:removeEventListener("collision", weap1)
self = nil
end
end
local type of variable (object) makes it work.
P.S.: vanshika, thanks for your answer, it's useful ;)

Related

Accessing the number of elements in an array and applying gravity behaviour

I'm having issues with getting ALL elements of an array to fall using the Gravity module. I have managed to get the LAST element in the array to fall and then the remaining elements just stay at the top of the screen during testing. Upon debugging
I am using UIKit and want to understand this language thoroughly before using other various engines such as SpriteKit and GameplayKit.
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
gravityBehavior = UIDynamicItemBehavior(items: [carView]) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: [carView, mainCar]) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
dynamicAnimator.addBehavior(collisionBehavior)
}
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
}
With the game so far the last car in the array falls and the main player car that I control has collision behaviour, which is a big step for me!
You are creating a new UIDynamicAnimator with each iteration of the loop and assigning it to dynamicAnimator. That is why only the last element is working, because it is the last one assigned to that variable.
To fix it, just move this line to somewhere that would only be called once.
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
viewDidLoad is a possible place that should work.
UIKitDynamics is backwards of most similar frameworks. You don't animate the object. You have an animator and attach objects to it. As Clever Error notes, you only want one animator in this case.
The key point is that you don't attach gravity to cars; you attach cars to behaviors (gravity), and then behaviors to the animator. Yes, that's bizarre and backwards.
I haven't tested this, but the correct code would be closer to this:
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
var carViews: [UIImageView] = []
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
// First create all the views
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
carViews.append(carView)
}
// and then attach those to behaviors:
gravityBehavior = UIGravityBehavior(items: carViews) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: carView + mainCar) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
dynamicAnimator.addBehavior(collisionBehavior)
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
// You don't need this; it's built into Gravity
// gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
}
The main way that UIKitDynamics is different than most animation frameworks is that things that are animated don't know they're being animated. You can't ask a car what behaviors it has, because it doesn't have any. A UIDynamicAnimator basically is a timing loop that updates the center and transform of its targets. There's really not anything fancy about it (in contrast to something like Core Animation which has many fancy things going on). With a little iOS experience, you could probably implement all of UIKitDynamics by hand with a single GCD queue (it probably doesn't even need that, since it runs everything on main....)

How do I update unlockable characters in SpriteKit game with Swift 3?

I have currently made a game featuring one player. I also made a character screen where the user can choose which character he/she wants to play with. How do I make it so that a certain high score unlocks a certain character, and allows the user to equip this character to use in the game?
Right now my player has his own swift file that defines all the properties of him:
import SpriteKit
class Player: SKSpriteNode, GameSprite {
var initialSize = CGSize(width:150, height: 90)
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Rupert")
let maxFlyingForce: CGFloat = 80000
let maxHeight: CGFloat = 900
var health:Int = 1
var invulnerable = false
var damaged = false
var damageAnimation = SKAction()
var dieAnimation = SKAction()
var forwardVelocity: CGFloat = 190
var powerAnimation = SKAction()
init() {
super.init(texture:nil, color: .clear, size: initialSize)
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
let bodyTexture = textureAtlas.textureNamed("pug3")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
self.physicsBody?.linearDamping = 0.9
self.physicsBody?.mass = 10
self.physicsBody?.allowsRotation = false
self.physicsBody?.categoryBitMask = PhysicsCategory.rupert.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.enemy.rawValue | PhysicsCategory.treat.rawValue | PhysicsCategory.winky.rawValue | PhysicsCategory.ground.rawValue
func createAnimations() {
let rotateUpAction = SKAction.rotate(toAngle: 0.75, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(toAngle: 0, duration: 0.475)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [
textureAtlas.textureNamed("pug1"),
textureAtlas.textureNamed("pug2"),
textureAtlas.textureNamed("pug3"),
textureAtlas.textureNamed("pug4"),
textureAtlas.textureNamed("pug3"),
textureAtlas.textureNamed("pug2")
]
let flyAction = SKAction.animate(with:flyFrames, timePerFrame: 0.07)
flyAnimation = SKAction.group([SKAction.repeatForever(flyAction), rotateUpAction])
let soarFrames:[SKTexture] = [textureAtlas.textureNamed("pug5")]
let soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAnimation = SKAction.group([SKAction.repeatForever(soarAction), rotateDownAction])
This is not all the code but you get the point.
I then say: let player = Player() in my Gamescene file which essentially attaches all the attributes in the player file to my player that will be seen in the Gamescene. Even if I am able to replace the initial player with a certain different player, there are so many animations that I don't know how to replace everything at once. I want to set a condition that spans over both the gamescene class and the player class so that it can just sub out certain images for other ones and keep the same actions.
Thank you for any help!
Here are some techniques you can use for making things like this more manageable:
Have a naming convention for your character images and/or sprite sheets, so that you can pass in a name to your Player() constructor. Then, instead of loading texturenamed("pug3"), you load up texturenamed("\(playerName)3"). If the only difference between your characters are the sprite sheets, this is actually all you need on the Player end.
If your characters are more complex, with differences beyond just the images, like being larger or having more health, then you will probably want to go with a more data-oriented approach. There are a couple of approaches to this, but a handy one is to read your texture names, hitbox sizes, health levels, etc., out of a .plist file instead of hard-coding them. Then just pass in the name of the .plist file to load for the character you want. Then, to create a new character, you just create a new .plist file. Another approach would be to create a "character definition" struct that you could pass to the Player constructor that contains the information you need to construct the player (this, in turn, could be loaded from a .plist file, as well, but you could also hard-code them or save them directly using codable serialization).
If neither of the above approaches are sufficient, say if you need different behavior between characters, you could always go the subclassing route - pull the various parts and pieces out into functions, and then override those functions to add the specific functionality you need for more complex characters.

Values printing in pairs issue? Lua

For some reason the countries that seem to be returned are all returning in pairs? How can you change the code so it only returns the countries in 'Europe' once?
function newcountry(continent,country)
local object = {}
object.continent = continent
object.country = country
local list = {}
for i in pairs( object ) do
if object.continent == "Europe" then
table.insert(list, object.country)
print(object.country)
end
end
return object
end
a = newcountry("Africa","Algeria")
b = newcountry("Europe","England")
c = newcountry("Europe","France")
d = newcountry("Europe","Spain")
e = newcountry("Asia","China")
I'm not sure what you are trying to accomplish with this code, but to answer your question:
function newcountry(continent,country)
local object = {}
object.continent = continent
object.country = country
local list = {}
if object.continent == "Europe" then
table.insert(list, object.country)
print(object.country)
end
return object
end
This code will print countries in Europe just once. When there was loop in there, it printed name of the country twice, because it did it for each element of object table (continent and country, hence two times).
Generic for loops in Programming in Lua (first edition).
I would also like to point out that list is quite useless at the moment. It is not being returned and stays local. On top of that, every time you call newcountry there is new list created. They are all unique - country objects are not added to single list. But again - I don't know what you are trying to accomplish.

Gideros event showing as null after being triggered

I have got Dispatched events in my gideros game. What im not certain of, is that when i try access the event (object triggered) it returns a table that has a length of 0.
I have a letter class as below:
GridLetter = gideros.class(Sprite)
function GridLetter:init(letter)
self.image = Bitmap.new(Texture.new("GridLetters/"..letter.."Grid.png"))
self.image:setAnchorPoint(0.5, 0.5)
self.focus = false
self:updateVisualState(true)
self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
end
function GridLetter:onMouseDown(event)
if self:hitTestPoint(event.x, event.y) then
self.focus = true
self:dispatchEvent(Event.new("GridLetterDown"))
event:stopPropagation()
end
end
function GridLetter:updateVisualState(state)
if state then
self:addChild(self.image)
end
end
The code that implements this class is below:
grid = Core.class(Sprite)
local letterArr = {"a","b","c","d","e","f","g","h","u","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"}
local gridboard = {{},{},{},{}}
local letterBoxArr = {{},{},{},{}}
local letterBox
function grid:init(params)
local widthOfSingle = (application:getContentWidth() / 4)
for rowCount = 1,4 do
for colCount = 1,4 do
rand = math.random(1, 26)
gridboard[rowCount][colCount] = letterArr[rand]
end
end
for rowCount = 1,4 do
for colCount = 1,4 do
letterBox = GridLetter.new(gridboard[rowCount][colCount])
letterBoxArr[rowCount][colCount] = {letterBoxItem=letterBox}
letterBoxArr[rowCount][colCount].letterBoxItem:setPosition(((widthOfSingle * colCount) - (widthOfSingle / 2)), 100 * rowCount)
letterBoxArr[rowCount][colCount].letterBoxItem.letter = gridboard[rowCount][colCount];
letterBoxArr[rowCount][colCount].letterBoxItem:addEventListener("GridLetterDown", LetterDown, self)
self:addChild(letterBoxArr[rowCount][colCount].letterBoxItem)
end
end
end
function LetterDown(event)
print(event.target.letter)
end
When i click on one of these image, the event is fired and code does run under the LetterDown event, but when trying to access the event parameter, it returns:
grid.lua:32: attempt to index field 'target' (a nil value)
stack traceback:
grid.lua:32: in function <grid.lua:31>
GridLetter.lua:15: in function <GridLetter.lua:12>
any idea or workarounds?
When replacing:
print(event.target.letter)
to
print(event.x)
It prints nil.
I appreciate your help in advance.
Regards,
Warren
This isn't an answer (I'd comment, but that strangely requires rep).
The error you're getting is because in event, there's nothing in the target field.
You could find out what's in the table by looping over it (or by using a pretty table print function, if glideros defines one/you define one yourself):
for k, v in pairs(target) do print(k, v) end
If you're not sure if target will exist all the time, you can also do something like this:
print(not event.target and "event.target does not exist" or event.target.letter)
You can read about the A and B or C construct here: http://lua-users.org/wiki/TernaryOperator
Edit: Assuming that the event you receive fires with a GridLetter object, you are not setting self.letter to anything. Perhaps setting the field will help:
function GridLetter:init(letter)
self.letter = letter -- Forgot this?
self.image = Bitmap.new(Texture.new("GridLetters/"..letter.."Grid.png"))
self.image:setAnchorPoint(0.5, 0.5)
self.focus = false
self:updateVisualState(true)
self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
end
I have got it.. Someone on gideros forums helped me out, thanks to Advert as well.
So in my onMouseDown event i was assigning the .letter on the general event. So creating a local var and assigning it the event before dispatching it, the variable then keeps my assigned letter.
Hope this helps others in future!
function GridLetter:onMouseDown(event)
if self:hitTestPoint(event.x, event.y) then
self.focus = true
local e = Event.new("GridLetterDown")
e.letter = self.letter
self:dispatchEvent(e)
event:stopPropagation()
end
end
Thanks for all responses.

Corona sdk having trouble stopping timers on individual spawned objects

I have a beat em up game and a 'punch' sound is played on a timer if an enemy collides with my character as shown below:
local function hitSound()
local hsound = math.random(#sfx.hit)
audio.play(sfx.hit[hsound])
end
--------------------------------------------------CHARACTER COLLISION---------------
local function guyCollision(self, event)
if event.other.type == "enemy1"then
if event.phase == "began" then
hitTimer = timer.performWithDelay(500,hitSound,0)
event.other:setSequence("punch")
event.other:play()
end
if event.phase == "ended" then
timer.pause(hitTimer)
end
This works fine when my character is taking down just one enemy at a time but if there are more than one spawned enemy (which there usually is) fighting my character when i kill them the punching sound remains.
Also when i call audio.fadeout() when my character dies the punch sounds don't fade out with the other sounds/music :s
Someone suggested to me to assign the timer to the specific enemy in question using its index in its table but im unsure of how to do this... is this the best way? I just don't know how to get that enemys current index.. any help much appreciated!
Heres my enemy spawn code:
local spawnTable2 = {}
local function spawnEnemy()
enemy1 = display.newSprite( group, sheetE, sequenceData2 )
enemy1.x=math.random(100,1300)
enemy1.y=math.random(360,760)
enemy1.gravityScale = 0
enemy1:play()
enemy1.type="coin"
enemy1.objTable = spawnTable2
enemy1.index = #enemy1.objTable + 1
enemy1.myName = "enemy" .. enemy1.index
physics.addBody( enemy1, "kinematic",{ density = 1.0, friction = 0.3, bounce = 0.2 })
enemy1.isFixedRotation = true
enemy1.type = "enemy1"
enemy1.enterFrame = moveEnemy
Runtime:addEventListener("enterFrame",enemy1)
enemy1.objTable[enemy1.index] = enemy1
return enemy1
end
I think your problem is that your hitTimer variable is probably being overwritten and you can only cancel the last one. You could probably do: self.hitTimer to get around it.
Rob

Resources