I am attempting to add/remove objects from the physics engine (addBody() and removeBody()) in an app I am working on. The app I am working on is modular so the issue is in one of two files.
The objects file (TransmitterObject) or the main file (main):
This is the relevant code for both:
main.lua
local physics = require("physics")
physics.start()
physics.setGravity(0,0)
physics.setDrawMode( "debug" )
local TransmitterObject = require("TransmitterObject")
function updateGame(event)
if(ITERATIONS % 100 == 0) then
tran1:activate() --create new physics object here
end
ITERATIONS = ITERATIONS + 1
--print(ITERATIONS)
end
Runtime:addEventListener("enterFrame", updateGame)
TransmitterObject.lua
function transmitter.new(props) --constructor
Transmitter =
{
x = props.x,
y = props.y,
receivers = props.receivers
}
return setmetatable( Transmitter, transmitter_mt )
end
function transmitter:activate()
local group = math.random(1, #self.receivers)
local receiver = math.random(1,#self.receivers[group])
local x , y = self.receivers[group][receiver][1], self.receivers[group][receiver][2]
local d = math.sqrt(math.pow((self.x-x),2) + math.pow((self.y-y),2))
local dx = math.abs(self.x - x)
local angle = math.deg(math.acos(dx/d))
local beam = display.newRect(self.x,self.y, d, 10)
beam:setReferencePoint(display.TopLeftReferencePoint)
beam.rotation = 180 + angle
beam:setFillColor(0,255,0)
beam.alpha = 0
local function add(event)
physics.addBody(beam, "static")
end
local function delete(event)
physics.removeBody(beam)
end
transition.to( beam, { time=1000, alpha=1.0, onComplete=add } )
transition.to( beam, { time=1000, delay=2500, alpha=0, onComplete=delete})
end
Now let me try to describe the issue a little better. basically every 100th time that 'enterFrame' fires I tell the transmitter object (tran1) to call its function 'activate'
which then preforms some basic math to get coordinates. Then it creates a rectangle (beam) using the calculated information and sets some properties. That is all basic stuff. Next I tell it to transition from not visible (alpha = 0) to visible over the span of 1 second. When does it is to call the function 'add' which adds the object to the physics engine. Likewise with the next line where it removes the objects.
That being said, when i set physics.setDrawMode( "debug" ) the beam object appears as a static body, but does not accept collisions. Does anyone know why the above code would not accept collisions for the beam object?
Keep in mind I have other objects that do work properly within the physics engine.
Wow, I'm answering super late!
On collisions, modifying bodies aren't supported.
What I propose you is to create a new function,
local function addBody ( event )
physics.addBody(ball, "static")
end
and in your collision event you have to add this,
timer.performWithDelay(500, addBody)
The only thing that may cause some problems it's the delay, but as the collision doesn't take too much time it should be ok.
Sorry for this necroposting,
It's just to help other people that may have that problem,
Fannick
Related
I'm creating a 2D platform game using Corona SDK and I'm stuck with collisions.
Basically there is a character that runs over this ground made of blocks. This is because sometimes there can be holes in the ground.
The game is an endless one, so as the character moves forward new blocks (and holes) are dynamically added - and also removed if they goes off screen.
It works nicely but this approach works against the collision system, let me explain how.
Now that I have the ground in place I want the character to jump but only if it is touching the ground, to avoid jumping while in air.
Whenever a collision is detected between character and ground an event is fired - two times. The first time when the character is entering a ground block and the second time when the character leaves it. So when the character lands on the ground a isGround Boolean is set to true. And when - after a jump - it leaves it the flag is set to false. The problem is that every time it exits a block to enter another - walking along the ground without jumping - the flag get updated. This makes the jump based on the isGround flag less reliable. Sometimes it happens that you can't jump because isGround == false though the character is on the ground.
Ground block creation snippet
-- init() method set the sprite of the ground block and physic to that sprite
function GroundBlock:init()
self.sprite = display.newImageRect(self.path, self.width, self.height)
self.sprite.x = self.x
self.sprite.y = self.y
physics.addBody(self.sprite, 'static', {
density = 0,
friction = 0,
bounce = 0,
box = {
halfWidth = self.width / 2,
halfHeight = self.height / 2,
y = 16,
x = 0
}
})
local collisionObj = {
name = 'ground'
}
self._collision = collisionObj
self.sprite._collision = collisionObj
self.isShow = true
end
Ground placing GroundBlocks snippet
-- init() method initialize the ground with a fixed number of blocks
function Ground:init()
self.offsetX = 0
while self.offsetX < self.camera.borderRight * 2 do
self._createBlock(1)
end
self.lastCameraPos = self.camera.borderRight
end
-- update() is called once per frame
function Ground:update()
if (self.camera.borderRight - self.lastCameraPos > self._blockWidth) then
local rand = math.ceil(math.random() * 10) % 2
if self._skippedBlock >= 2 or rand == 0 then
self._createBlock(1)
self._skippedBlock = 0
else
self._createBlock(0)
self._skippedBlock = self._skippedBlock + 1
end
self.lastCameraPos = self.camera.borderRight
end
for i, block in ipairs(self.blocks) do
if block.sprite.x < self.camera.borderLeft - block.width then
table.remove(self.blocks, i)
self.camera:remove(block.sprite)
block:delete()
end
end
end
Collision detection snippet
function Character:collision(event)
if ( event.phase == "began" ) then
if event.other._collision.name == "ground" then
self.isGround = true
end
elseif ( event.phase == "ended" ) then
if event.other._collision.name == "ground" then
self.isGround = false
print('nope')
end
end
end
A solution would be to make a ground as a single imgRect but how to make holes in it?
You could simplify your code and prevent this issue from ever occurring by tracking if the character can jump instead of tracking if the character is on the ground.
For instance,
function jump( event )
if event.phase == "began" then
if canJump then
canJump = false
-- your code that makes the player jump
end
end
end
You probably use touches to determine whether the player character jumps, right? This way, you'll trigger the jump when the touch starts as long as the character has not already jumped.
You could then reset this value in your collision function by editing it slightly:
function Character:collision(event)
if event.phase == "began" then
if event.other._collision.name == "ground" then
canJump = true
end
end
end
This way, the character's ability to jump is determined by whether or not the player has pressed jump already and if the character has hit the ground since the last jump.
This kind of approach also gives you the ability to pivot towards implementing mechanics like double jump. If instead of using a boolean canJump variable you chose to use a number variable, e.g. jumpsLeft, you could reduce the number of jumps left every time the character jumps and only let the character jump if jumpsLeft is larger than 0. Then you'd simply reset the value back to 1 (or whatever you'd want upon hitting the ground).
I am currently trying to work out how I should go about shooting projectiles using Corona SDK. However, I don't know the best way to go about doing this. I am guessing that you have to spawn instances of the same object and apply force to them but I don't know the best way to do it or how I should handle each instance. I am still learning Lua and just need some guidance on how to do it, any help will be appreciated.
I want to be able to check if any of the bullets hit a sensor object ( I haven't implemented this but I know how to ) at the top of the screen and then destroy the bullet that hit the sensor but how do I check each instance and destroy them individually?
This is the basic structure that I have so far.
display.setStatusBar( display.HiddenStatusBar )
local physics = require( 'physics' )
physics.start()
local speed = -500
local contentW, contentH = display.contentWidth, display.contentHeight
-- Background
local bg = display.newRect( 0, 0, contentW, contentH )
bg.anchorX = 0
bg.anchorY = 0
bg:setFillColor( 0, 1, 1 )
-- Ground
local ground = display.newRect( 0, contentH - 50, contentW, 50 )
ground.anchorX = 0
ground.anchorY = 0
ground:setFillColor( 0, 0.8, 0 )
-- Hero
local hero = display.newRect( contentW / 2, contentH / 2, 40, 40 )
hero:setFillColor( 1, 0, 0 )
function shoot( event )
if ( event.phase == 'began' ) then
local projectile = display.newRect( hero.x, hero.y, 10, 30 )
physics.addBody( projectile, 'dynamic' )
projectile.gravityScale = 0
projectile.isBullet = true
projectile:setLinearVelocity( 0, -600 )
end
end
Runtime:addEventListener( 'touch', shoot )
This was suggested on the Corona forums by the Corona staff.
To remove each bullet that hits the sensor you need to give the projectile a 'type' of something along those lines. Note that you can use any word instead of 'type', but this is my preferred way.
projectile.type = 'bullet'
Then, you need to add an event listener to the sensor object that detects the collision, in this case it is an object called 'wall'. On collision you want to remove the other object that was in the collision ( the bullet ). You can do this like so.
local function wallCollision( event )
if event.phase == 'began' then
if event.other.type == 'bullet' then
display.remove( event.other )
event.other = nil
end
end
end
wall:addEventListener( 'collision', wallCollision )
'event.other' targets the other object involved in the collision event, in this case, the 'bullet'.
Not sure this is what you are after, but the strategy to handle evolving multiple objects which can be removed later as a result of collision is:
create the bullet display object, with a collision handler
in the collision handler, if object needs to be removed then use removeSelf; other changes may require delayed change as explained in Modifying Objects.
So in your shoot function you would add, after projectile:setLinearVelocity:
projectile.collision = function (event)
...
if remove then
self:removeSelf()
end
...
end
projectile:addEventListener( "collision", projectile)
This adds the handler to each bullet. You could instead add just one handler for the sensor, it would be similar code which you would put right after creating the sensor, except that you remove the event.other instead of self:
sensor.collision = function (event)
...
if remove then
event.other:removeSelf()
end
...
end
sensor:addEventListener( "collision", sensor)
I have a model called door
Inside I have a BoolValue named Open
I have a model called Top that has all of the door blocks named Work Mabey Comeon and Proboblynot
And I have Block that when touched is supposed to make Top move up
Directly inside door I have this script
door = script.Parent
open = door.Open
Top = door.Top
opener = 18
speed = 100
steps = speed
startl = Top.CFrame
function MoveDoorToCFrame(cfrm,dr)
dr.Work.CFrame = cfrm
dr.Mabey.CFrame = dr.Work.CFrame * CFrame.new(0,-7.2,0)
dr.Comeon.CFrame = dr.Work.CFrame * CFrame.new(0,10.8,0)
dr.Problynot.CFrame = dr.Work.CFrame * CFrame.new(0,10.8,0)
end
function Update()
if speed/steps < 0.5 then
calc = 1-math.cos(math.rad((-90/speed)*steps*2))
else
calc = 1+math.sin(math.rad((90/speed)*((speed/2)-steps)*2))
end
MoveDoorToCFrame(startl * CFrame.new(0,(calc/2)*opener,0),Top)
end
Update()
while true do
wait()
if not open.Value and steps < speed then
steps = steps + 1
Update()
elseif open.Value and steps > 0 then
steps = steps - 1
Update()
end
end
Inside the button that is supposed to activate on touch I have
script.Parent.Touched:connect(function()
script.Parent.Parent.Open.Value = not script.Parent.Parent.Open.Value
end)
script.Parent.Parent.Open.Changed:connect(Update)
Update()
If you know how to fix this it would be gladly appreciated.
Update November 2015:
Using PrimaryPart
Since writing this post, ROBLOX has changed a lot in regards to the API. To move a model like requested, you should set the PrimaryPart property of the model to a central part inside the model. This will act as the origin for the model's movements.
You can then use model:SetPrimaryPartCFrame(cframe) to set the CFrame of the model. You can also retrieve this property by using model:GetPrimaryPartCFrame(), although I believe that is just a shortcut method for model.PrimaryPart.CFrame.
In code, it would look like this:
-- Set PrimaryPart:
MODEL.PrimaryPart = MODEL.SomeCentralPart
...
-- CFrame movement:
local movement = CFrame.new(0, 10, 0)
-- Move the model:
MODEL:SetPrimaryPartCFrame(MODEL:GetPrimaryPartCFrame() * movement)
Option A: Use Model's methods
I think you are making this much more difficult than it needs to be. Whenever you run into an issue like this, be sure to check the current APIs provided. The ROBLOX Model object contains a nifty method called 'TranslateBy' which takes a Vector3 argument to translate the model.
Using MODEL:TranslateBy(Vector3) is similar to moving a model via CFrame, since it ignores collisions.
Another alternative is MODEL:MoveTo(Vector3) which moves a whole model to the given Vector3 world position. The downside to this is that it does collide.
One way to get the same MoveTo effect but without collisions can be done with the TranslateBy method:
MODEL:TranslateBy(Vector3Position - MODEL:GetModelCFrame().p)
Option B: Write a custom function to manipulate the model's CFrame
Another alternative would be to manipulate the whole model's CFrame entirely. To do this, you can write a clever function that will move a whole model relative to an 'origin' point. This is similar to moving shapes on a grid given their points and an origin, except in three dimensions. Using ROBLOX's built-in functions, this is much easier though.
A good way to do this would be to write a function that lets you actually assign a CFrame value to a whole model. Another way would be to allow a translation via CFrame too.
Here's an example:
function ModelCFrameAPI(model)
local parts = {} -- Hold all BasePart objects
local cf = {} -- API for CFrame manipulation
do
-- Recurse to get all parts:
local function Scan(parent)
for k,v in pairs(parent:GetChildren()) do
if (v:IsA("BasePart")) then
table.insert(parts, v)
end
Scan(v)
end
end
Scan(model)
end
-- Set the model's CFrame
-- NOTE: 'GetModelCFrame()' will return the model's CFrame
-- based on the given PrimaryPart. If no PrimaryPart is provided
-- (which by default is true), ROBLOX will try to determine
-- the center CFrame of the model and return that.
function cf:SetCFrame(cf)
local originInverse = model:GetModelCFrame():inverse()
for _,v in pairs(parts) do
v.CFrame = (cf * (originInverse * v.CFrame))
end
end
-- Translate the model's CFrame
function cf:TranslateCFrame(deltaCf)
local cf = (model:GetModelCFrame() * deltaCf)
self:SetCFrame(cf)
end
return cf
end
-- Usage:
local myModel = game.Workspace.SOME_MODEL
local myModelCF = ModelCFrameAPI(myModel)
-- Move to 10,10,10 and rotate Y-axis by 180 degrees:
myModelCF:SetCFrame(CFrame.new(10, 10, 10) * CFrame.Angles(0, math.pi, 0))
-- Translate by 30,0,-10 and rotate Y-axis by 90 degrees
myModelCF:TranslateCFrame(CFrame.new(30, 0, -10) * CFrame.Angles(0, math.pi/2, 0))
This might be hard.
You might want to look to free models for this one unless the people above get it to work.
I, however, do have a script to move a model:
game.Workspace.Model:MoveTo(Vector3.new(0,0,0))
Your code indeed needs fixing.
You should NOT use a never-ending loop to make your stuff work (unless that is the only way).
You should rather base actions on events.
Consider to use this:
Structure:
Door [Model]
DoorScript [Script]
Button [Part]
DoorOpen [BoolValue]
Top [Model]
Mabey [Part]
Comeon [Part]
Problynot [Part]
DoorScript:
local Model = script.Parent
local Door = Model.Top
local Button = Model.Button
local DoorOpen = Model.DoorOpen
local Offset = 0
local ToOffset = 100
local Direction = 1
local StepLength = 0.1
local Moving = false
function StartMoving()
if Moving then return end
Moving = true
while (DoorOpen.Value and Offset ~= ToOffset) or (not DoorOpen.Value and Offset ~= 0) do
local Change = Offset
Offset = math.max(0,math.min(ToOffset,Offset + StepLength * (DoorOpen.Value and 1 or -1)))
Change = Offset - Change
Top:TranslateBy(Vector3.new(0,Change,0))
wait()
end
Moving = false
end
StartMoving()
DoorOpen.Changed:connect(StartMoving)
local Debounce = false
Button.Touched:connect(function()
if Debounce then return end
Debounce = true
DoorOpen.Value = not DoorOpen.Value
wait(4)
Debounce = false
end)
You might want to adjust the speed tho.
This can be used to move models, try adding something like this into your code. It's more dynamic.
a = Workspace.Model
for i=0.1,40 do
for i,v in pairs(a:getChildren()) do
if v:IsA("Part") then
v.CFrame = CFrame.new(v.CFrame + Vector3.new(0,0.1,0))
else print("Not a part")
end
end
end
I've created a sprite sheet(of a frog jumping) using texture packer and I am trying to get the character to jump forward when I click on the sprite. I've created an event listener and when I click on the sprite the play() method animates the sprite. But I can't get the sprite to jump forward using the applyForce or setLinearVelocity methods? Here is my code:
require("physics")
local sprite = require "sprite"
local sheetData = require "myFrogs" -- name of file created using texturepacker
physics.start()
physics.setGravity(0,1)
local _w = display.contentWidth/2
local _h = display.contentHeight/2
local spriteData = sheetData.getSpriteSheetData()
local spriteSheet = sprite.newSpriteSheetFromData("images/myFrogs.png", spriteData)
local spriteSet = sprite.newSpriteSet(spriteSheet, 1, 7) number of images in spritesheet
local frogSprite = sprite.newSprite(spriteSet)
frogSprite.x = _w
frogSprite.y = _h
physics.addBody(frogSprite, "static", {friction=1.0,density=1.0,bounce=0.3, radius=35})
frogSprite.isFixedRotation = true
local function frogJump(event)
if(event.phase == "ended") then
--frogSprite:applyForce() -- should I use this method
--frogSprite:setLinearVelocity() -- or this method
frogSprite:play()
end
end
frogSprite:addEventListener("touch", frogJump)
You made the frog's body static - switch to dynamic and it will jump using either of those methods.
By making frog body static, it will not be affected by impulse or force so it should be Dynamics
Body:applyForce( xForce, yForce, bodyX, bodyY ) <- Apply Force to your center of mass i.e Reference point you have set.
Body:setLinearVelocity( xVelocity, yVelocity ) <- It will give a velocity of data to will provide in terms of pixel per second.
I'm trying to imitate an iPad-style inertia scroll in Corona, using the touch event and the enterFrame event. In short, I should be able to drag and "throw" an object to a certain extent, similar to scrolling on the iPad. I'm fairly new to Corona, but I've used other languages before. (This is my second day.)
This is the code I've got so far:
local bg = display.newImage("cloud.jpg");
bg:setReferencePoint(bg.TopLeftReferencePoint);
bg.x = 0;
bg.y = 0;
function bg:touch (event)
print("event", event)
for i,v in pairs(event) do
print("**: ",i,v)
end
bg.x = event.x;
bg.y = event.y;
bg.xStart = event.xStart;
bg.yStart = event.yStart;
if (event.phase == "ended")
then
bg.xdelta = bg.xStart + bg.x;
bg.ydelta = bg.yStart + bg.y;
Runtime.addEventListener("enterFrame", bg);
end
end
function bg:enterFrame(event)
bg.x = bg.x + bg.xdelta;
bg.y = bg.y + bg.ydelta;
// TODO: Add code to decrease delta so that object gradually stops.
end
bg:addEventListener("touch");
This is throwing an error in the compiler. What am I doing wrong? I tried making the enterFrame function a local function instead of a table function, but I ran into the same issue. I'm sure that the answer is VERY simple, but I'm not familiar enough with Corona to see it immediately.
Edit: I've done some reading and I've realized that this is closer to what I want:
local bg = display.newImage("cloud.jpg");
bg:setReferencePoint(bg.TopLeftReferencePoint);
bg.x = 0;
bg.y = 0;
bg.xdelta = 0;
bg.ydelta = 0;
local function onEveryFrame(event)
bg.x = bg.x + (bg.oldx - bg.x);
bg.y = bg.y + (bg.oldy - bg.y);
end
function bg:touch (event)
if (event.phase == "ended")
then
print("ended")
bg.oldx = bg.x;
bg.oldy = bg.y;
bg.x = event.x;
bg.y = event.y;
Runtime.addEventListener("enterFrame", onEveryFrame)
end
print("event", event)
for i,v in pairs(event) do
print("**: ",i,v)
end
bg.oldx = bg.x;
bg.oldy = bg.y;
bg.x = event.x;
bg.y = event.y;
print("bg.x:", bg.x)
print("bg.oldx:", bg.oldx)
print("bg.y:", bg.y)
print("bg.oldy:", bg.oldy)
end
bg:addEventListener("touch");
This is no longer erroring out, but I'm not getting the desired result either...
I have not attempted to solve this problem in Corona, but encountered it a lot in Director when implementing smooth scrolling.
While dragging, capture a velocity that measures speed over several touch events, comparing position differences to timestamp differences. Use a table or a linked list for the individual measuring points, and average the velocity over the stored values.
Then the "ended" event in the same position as the last "moved" event will only slightly decrease speed, preserving inertia. In absence of dragging events, subtract a value from the velocity for friction.
Actually, you have a syntax error.
It is Runtime:AddEventListener (pay attention to the : instead of .)
EDIT:
Now about your math:
Your logic do: during a moved event, it sets the bg.p (p for position, both x and y) to event.p
During the ended event you set bg.olp to bg.p and then bg.p to event.p and then you try to every frame do bg.p+=(bg.oldp-bg.p)
This maybe would work IF you did the olp logic on "moved" because the result is this:
Users move finger: moved event triggers, and bg.p changed (thus you see it moving).
Users attempt to remove finger: This trigger a "ended" event, that has a very high change that it will trigger in the same place as the last "moved" event (you would need to move the finger VERY, VERY, VERY fast to trigger a "ended" far from the last "moved"). This will make your bg.oldp be the same as bg.p and thus you do bg.p+=(bg.olp-bg.p) the result is bg.p+=0