I'm using Xamarin Forms and I have a UIImage (in a custom renderer) that I load from a file and that I use as a pattern.
The image is a 4x16 pixels with two 4x4 pixels black areas and a 4x8 transparent area:
I need to change the color of the black areas dinamically.
This is what I tried, without any success:
UIImage image = UIImage.FromFile("line_pattern.png");
image = image.ApplyTintColor(UIColor.Orange,UIImageRenderingMode.AlwaysTemplate);
The image is loaded correctly, but the color doesn't change. How can I do it?
Have a try to set the tintColor to imageView:
UIImageView imageView = new UIImageView();
UIImage image = UIImage.FromFile("line_pattern.png");
image = image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
image = image.ApplyTintColor(UIColor.Orange, UIImageRenderingMode.AlwaysTemplate);
imageView.Image = image;
imageView.TintColor = UIColor.Orange;
Setting the tint color works for PNGs with a single color.
However, we wanted to be able to change individual colors in UIImage objects loaded from JPGs or PNGs which may have more than one color. Our target images are icons or similar images that have a relatively limited number of distinct colors, and swapping one color for another seems like it should be simple, but none of the solutions we found online provided exactly what we were looking for.
So, to replace a color we created the following filter class derived from CIColorCube. We use this with images that have a relatively limited number of colors, but nothing prevents this from being used with more complex images. If you don't get the results you want, play with the CubeDimension and IndexPlusMinus properties to see how they affect the image.
This code is loosely based on the Apple Developer Chroma Key Filter Recipe example. The logic used there for setting the color map may be more appropriate for complex images like photos.
Finally, for anyone updating from Xamarin to MAUI, this code works on MAUI by simply changing the one nfloat reference to NFloat.
UPDATE: The sushihangover/ColorReplaceFilter.cs can also be used to replace colors. In fact, we had originally tried using the sushihangover code but had problems, so we created the color cube filter. Ultimately the problems we had with the sushihangover filter were our own doing; it works just fine once we figured that out. The performance of both the color cube filter and the sushihangover filter seem to be about the same, so either filter will do the job.
public class iOSReplaceColorFilter : CIColorCube
{
/// <summary>
/// The color to replace with NewColor. If not set, the color of all non-transparent
/// pixels in the image will be changed to NewColor.
/// </summary>
public CIColor OldColor { get; set; }
/// <summary>
/// The new color used to replace the old color.
/// </summary>
public CIColor NewColor { get; set; }
/// <summary>
/// An offset that is applied when calculating which color cube index values match
/// the old color. Set this to 1 to avoid interpolation of the new color when the
/// RGB color indices of the old color do not exactly resolve to integer values.
/// </summary>
public int IndexPlusMinus { get; set; } = 1;
/// <summary>
/// Default constructor. To use this, you must explictly set OldColor, NewColor, and
/// the CubeDimension, then call InitCube before getting the OutputImage.
/// </summary>
public iOSReplaceColorFilter()
{
}
/// <summary>
/// Create a replacement filter that uses the specified cube dimension for the color
/// cube map. The default dimension is 16, but cube dimensions as small as 2 can be
/// used. When replacing a color in an image with 2 or more distinct colors, the cube
/// dimension determines how close two colors' RGB values can be without introducing
/// interpolation effects.
/// </summary>
public iOSReplaceColorFilter(CIColor oldColor, CIColor newColor, int cubeDimension = 16)
{
OldColor = oldColor;
NewColor = newColor;
CubeDimension = cubeDimension;
InitCube();
}
/// <summary>
/// Create a replacement filter for replacing all non-transparent pixels in the image
/// with the specified color.
/// </summary>
public iOSReplaceColorFilter(CIColor newColor)
{
NewColor = newColor;
CubeDimension = 2;
InitCube();
}
/// <summary>
/// Build the color cube. This must be called before using OutputImage to get
/// the converted image.
/// </summary>
public virtual iOSReplaceColorFilter InitCube()
{
var dim = (int)CubeDimension;
var dimFactor = (float)(dim - 1);
var rangeChecker = new IndexRangeChecker(OldColor, dimFactor, IndexPlusMinus);
var offset = 0;
var cubeData = new float[dim * dim * dim * 4];
for (var b = 0; b < dim; ++b)
{
var blue = b / dimFactor;
for (var g = 0; g < dim; ++g)
{
var green = g / dimFactor;
for (var r = 0; r < dim; ++r)
{
var red = r / dimFactor;
if (NewColor != null && rangeChecker.Matches(r, g, b))
{
cubeData[offset++] = (float)NewColor.Red;
cubeData[offset++] = (float)NewColor.Green;
cubeData[offset++] = (float)NewColor.Blue;
}
else
{
cubeData[offset++] = red;
cubeData[offset++] = green;
cubeData[offset++] = blue;
}
cubeData[offset++] = 1.0f;
}
}
}
var byteArray = new byte[cubeData.Length * 4];
Buffer.BlockCopy(cubeData, 0, byteArray, 0, byteArray.Length);
var data = NSData.FromArray(byteArray);
CubeData = data;
return this;
}
/// <summary>
/// Checks to see if an r,g,b index set falls within the range of cube indices
/// that should be considered a "hit" on the target color. If the checker is
/// created with a null color, then all indices match.
/// </summary>
private class IndexRangeChecker
{
private IndexRange rRange;
private IndexRange gRange;
private IndexRange bRange;
public IndexRangeChecker(CIColor color, float dimFactor, int indexPlusMinus)
{
if (color != null)
{
rRange = new IndexRange(color.Red, dimFactor, indexPlusMinus);
gRange = new IndexRange(color.Green, dimFactor, indexPlusMinus);
bRange = new IndexRange(color.Blue, dimFactor, indexPlusMinus);
}
}
public bool Matches(int r, int g, int b)
{
if (rRange != null)
return rRange.Matches(r) && gRange.Matches(g) && bRange.Matches(b);
else
return true;
}
private class IndexRange
{
private int start;
private int end;
public IndexRange(nfloat colorValue, float dimFactor, int offset)
{
var indx = (int)Math.Round(dimFactor * colorValue);
start = indx - offset;
end = indx + offset;
}
public bool Matches(int indx) => start <= indx && indx <= end;
}
}
/// <summary>
/// Convenience function that creates and applies a iOSReplaceColorFilter to
/// an image and returns the resulting image.
/// </summary>
public static UIImage ReplaceColor(UIImage inputImage, CIColor oldColor, CIColor newColor)
{
var filter = new iOSReplaceColorFilter(oldColor, newColor);
filter.InputImage = new CIImage(inputImage);
var outputImage = iOSColorCubeUtil.RenderFilteredImage(filter.OutputImage, inputImage);
return outputImage;
}
}
public class iOSColorCubeUtil
{
/// <summary>
/// Render an image after all filters have been applied. If you have more than one
/// filter, the output of each should be passed to the next filter, and only the final
/// filter result should be passed to this function.
///
/// For example, to apply a sepia tone filter and then an invert filter, do this:
///
/// var sepiaFilter = new CISepiaTone { Intensity = 0.8f, InputImage = myImage };
/// var invertFilter = new CIColorInvert { InputImage = sepiaFilter.OutputImage };
/// var finalImage = RenderFilteredImage(invertFilter.OutputImage);
///
/// The originalImage is needed as a argument in order to render the image using the
/// same scale factor and orientation as the original image.
/// </summary>
public static UIImage RenderFilteredImage(CIImage filteredImage, UIImage originalImage)
{
// The ColorSpace MUST be set in order for the CIColorCube mapping to work. If
// the color space isn't set, CIColorCube-based color swapping only worked for
// target colors whose RGB color values were all either 0x00 or 0xFF (e.g. #000000,
// #FFFFFF, #FF0000, #FF00FF, etc.)
var rgbColorSpace = CGColorSpace.CreateDeviceRGB();
var options = new CIContextOptions
{
WorkingColorSpace = rgbColorSpace,
OutputColorSpace = rgbColorSpace
};
var context = CIContext.FromOptions(options);
var cgImage = context.CreateCGImage(filteredImage, filteredImage.Extent);
var fixedImage = UIImage.FromImage(cgImage, originalImage.CurrentScale, originalImage.Orientation);
return fixedImage;
}
}
Related
Getting poor quality picture in Windows UWP if taken immediately after starting preview. TakePhotoAsync() is called in StartPreviewAsync() task then part. Consistently getting poor quality pictures. How to get good quality picture immediately after starting preview?. Code is based on CameraStarterKit.
/// <summary>
/// Initializes the MediaCapture, registers events, gets camera device information for mirroring and rotating, starts preview and unlocks the UI
/// </summary>
/// <returns></returns>
task<void> MainPage::InitializeCameraAsync()
{
DBOUT(__func__);
WriteLine("InitializeCameraAsync");
// Get available devices for capturing pictures
return FindCameraDeviceByPanelAsync(Windows::Devices::Enumeration::Panel::Back)
.then([this](DeviceInformation^ camera)
{
if (camera == nullptr)
{
WriteLine("No camera device found!");
return;
}
// Figure out where the camera is located
if (camera->EnclosureLocation == nullptr || camera->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Unknown)
{
// No information on the location of the camera, assume it's an external camera, not integrated on the device
_externalCamera = true;
}
else
{
// Camera is fixed on the device
_externalCamera = false;
// Only mirror the preview if the camera is on the front panel
_mirroringPreview = (camera->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Front);
}
_mediaCapture = ref new Capture::MediaCapture();
// Register for a notification when video recording has reached the maximum time and when something goes wrong
_recordLimitationExceededEventToken =
_mediaCapture->RecordLimitationExceeded += ref new Capture::RecordLimitationExceededEventHandler(this, &MainPage::MediaCapture_RecordLimitationExceeded);
_mediaCaptureFailedEventToken =
_mediaCapture->Failed += ref new Capture::MediaCaptureFailedEventHandler(this, &MainPage::MediaCapture_Failed);
auto settings = ref new Capture::MediaCaptureInitializationSettings();
settings->VideoDeviceId = camera->Id;
settings->StreamingCaptureMode = Capture::StreamingCaptureMode::AudioAndVideo;
// Initialize media capture and start the preview
create_task(_mediaCapture->InitializeAsync(settings))
.then([this]()
{
_isInitialized = true;
return StartPreviewAsync()
.then([this]()
{
UpdateCaptureControls();
});
}).then([this](task<void> previousTask)
{
try
{
previousTask.get();
}
catch (AccessDeniedException^)
{
WriteLine("The app was denied access to the camera");
}
});
});
}
/// <summary>
/// Starts the preview and adjusts it for for rotation and mirroring after making a request to keep the screen on
/// </summary>
/// <returns></returns>
task<void> MainPage::StartPreviewAsync()
{
DBOUT(__func__);
// Prevent the device from sleeping while the preview is running
_displayRequest->RequestActive();
// Set the preview source in the UI and mirror it if necessary
PreviewControl->Source = _mediaCapture.Get();
PreviewControl->FlowDirection = _mirroringPreview ? Windows::UI::Xaml::FlowDirection::RightToLeft : Windows::UI::Xaml::FlowDirection::LeftToRight;
// Start the preview
return create_task(_mediaCapture->StartPreviewAsync())
.then([this](task<void> previousTask)
{
_isPreviewing = true;
FN_TRACE("CameraStreamState::Streaming %d\n", _mediaCapture->CameraStreamState);
TakePhotoAsync();
// Only need to update the orientation if the camera is mounted on the device
if (!_externalCamera)
{
return SetPreviewRotationAsync();
}
// Not external, just return the previous task
return previousTask;
});
}
/// <summary>
/// Takes a photo to a StorageFile and adds rotation metadata to it
/// </summary>
/// <returns></returns>
task<void> MainPage::TakePhotoAsync()
{
DBOUT(__func__);
// While taking a photo, keep the video button enabled only if the camera supports simultaneously taking pictures and recording video
VideoButton->IsEnabled = _mediaCapture->MediaCaptureSettings->ConcurrentRecordAndPhotoSupported;
// Make the button invisible if it's disabled, so it's obvious it cannot be interacted with
VideoButton->Opacity = VideoButton->IsEnabled ? 1 : 0;
auto inputStream = ref new Streams::InMemoryRandomAccessStream();
// Take the picture
WriteLine("Taking photo...");
return create_task(_mediaCapture->CapturePhotoToStreamAsync(Windows::Media::MediaProperties::ImageEncodingProperties::CreateJpeg(), inputStream))
.then([this, inputStream]()
{
return create_task(_captureFolder->CreateFileAsync("SimplePhoto.jpg", CreationCollisionOption::GenerateUniqueName));
}).then([this, inputStream](StorageFile^ file)
{
WriteLine("Photo taken! Saving to " + file->Path);
// Done taking a photo, so re-enable the button
VideoButton->IsEnabled = true;
VideoButton->Opacity = 1;
auto photoOrientation = ConvertOrientationToPhotoOrientation(GetCameraOrientation());
return ReencodeAndSavePhotoAsync(inputStream, file, photoOrientation);
}).then([this](task<void> previousTask)
{
try
{
previousTask.get();
}
catch (Exception^ ex)
{
// File I/O errors are reported as exceptions
WriteException(ex);
}
});
}
I am creating a card game in unity. In that, I need to flip a card in diagonal. I have tried using rotate and translate methods, but unfortunately I was unable to archive the target. I have attached the YouTube link with this thread. Anyone can help me to overcome this issue?
https://youtu.be/j5lBJYSSX2A
I have this code for card fliping. You will need to change the numbers of the rotation to rotate it diagonally but it should work for you
IEnumerator FlipCard()
{
yield return StartCoroutine(Constants.CardFlipTime.Tweeng((u) => gameObject.transform.localEulerAngles = new Vector3(0f, u, 0f),0, 90f));//begin the rotation
GetComponent<Image>().sprite = cardFrame;// change the card sprite since it currently not visible
Debug.Log("Rotated 90 deg");
yield return StartCoroutine(Constants.CardFlipTime.Tweeng((u) => gameObject.transform.localEulerAngles = new Vector3(0f, u, 0f), 90, 0f));//finish the rotation
}
And here is the Tweeng function I use to do the smooth lerp of values :
/// <summary>
/// Generic static method for asigning a action to any type
/// </summary>
/// <typeparam name="T"> generic</typeparam>
/// <param name="duration">Method is called on the float and same float is used as the duration</param>
/// <param name="vary">Action to perform over given duration</param>
/// <param name="start">Starting value for the action</param>
/// <param name="stop">End value of the action</param>
/// <returns>null</returns>
public static IEnumerator Tweeng<T>(this float duration, Action<T> vary,T start, T stop)
{
float sT = Time.time;
float eT = sT + duration;
Delegate d;
if (typeof(T) == typeof(float))
d = (Func<float, float, float, float>)Mathf.SmoothStep;
else if (typeof(T) == typeof(Vector3))
d = (Func<Vector3, Vector3, float, Vector3>)Vector3.Lerp;
else if (typeof(T) == typeof(Quaternion))
d = (Func<Quaternion, Quaternion, float, Quaternion>)Quaternion.RotateTowards;
else
throw new ArgumentException("Unexpected type " + typeof(T));
Func<T, T, float, T> step = (Func<T, T, float, T>)d;
while (Time.time < eT)
{
float t = (Time.time - sT) / duration;
vary(step(start, stop, t));
yield return null;
}
vary(stop);
}
you can read more about it and how to use it from this question
I find the official unity training https://www.youtube.com/watch?v=D5MqLcO6A8g
and find bug.(look at the score)
I spent about 2 days to fix it and failed.
I find the "DontGoThroughThings" script and try to rewrite to use in 2D. Failed again)
Please help me!
This is rewrite script:
public LayerMask layerMask; //make sure we aren't in this layer
public float skinWidth; //probably doesn't need to be changed
private float minimumExtent;
private float partialExtent;
private float sqrMinimumExtent;
private Vector2 previousPosition;
private Rigidbody2D myRigidbody;
//initialize values
void Awake()
{
myRigidbody = GetComponent<Rigidbody2D>();
previousPosition = myRigidbody.position;
minimumExtent = Mathf.Min(Mathf.Min(GetComponent<Collider2D>().bounds.extents.x, GetComponent<Collider2D>().bounds.extents.y));
partialExtent = minimumExtent * (1.0f - skinWidth);
sqrMinimumExtent = minimumExtent * minimumExtent;
}
void FixedUpdate()
{
//have we moved more than our minimum extent?
Vector2 movementThisStep = myRigidbody.position - previousPosition;
float movementSqrMagnitude = movementThisStep.sqrMagnitude;
if (movementSqrMagnitude > sqrMinimumExtent)
{
float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);
//RaycastHit2D hitInfo;
//check for obstructions we might have missed
if (Physics2D.Raycast(previousPosition, movementThisStep, movementMagnitude, 0, layerMask.value))
myRigidbody.position = (movementThisStep/movementMagnitude)*partialExtent;
Debug.DrawLine(myRigidbody.position, myRigidbody.position - previousPosition, Color.green);
}
previousPosition = myRigidbody.position;
}
This is unitypackage https://www.dropbox.com/s/a3n1dalbc1k0k42/Hat%20Trick.unitypackage?dl=0
P.S. Sorry for my english and thank you for help!!
Explanation
Continuous collision detection in Unity does not use raycasting. As a result, very fast moving (and/or comparatively small) objects (let's call that kind of object projectile for now) still pass through things without a collision being detected. The famous DontGoThroughThings component fixes that for 3D. There are a few inconsistencies, but it can get the job done if you know what you are doing.
Here is my 2D adaption of it.
I added some features to make it more user-friendly to everyone who is not that good at coding or game physics.
How to use it
Add this component to your fast moving objects and they will always trigger the OnTriggerEnter2D event when hitting something.
You can also chose to send a different, custom message instead (by changing the MessageName variable). I actually recommend that due to the caveat explained below.
Sending messages is the script's primary use case. It will not magically make a projectile behave correctly in a physical sense.
The triggerTarget variable determines whether it sends the message to itself (in case, you have the hit handling script attached to the projectile), to the object being hit (in case, you have the hit handling attached to the objects that should be hit by projectiles), or any variation of the two.
Unlike the original version, this script also allows for force to be applied upon impact which can be tuned through the momentumTransferFraction variable. When two objects collide the force generated is a result of a transfer of momentum (mass times velocity) between the two objects. The way I do this is very rudimentary and it is missing a lot of contributing factors, but it is enough to have projectiles push objects on impact. Just like in the real world, the faster or heavier your projectile is, the more force is exerted.
There are also some caveats (most of which also apply to the original version)
Only apply on this on very fast moving objects. The less you use it the better because it is quite a bit more computationally expensive than the normal collision detection.
While collision detection is more accurate, Collision resolution is only very rudimentary. It is not as good as what the physics engine does by default.
In the current version, collisions are always detected in hindsight. That is why you might see projectiles having gone through the object by the time the collision is registered. I want to fix that in the near future.
If you use this on objects like bullets or other forms of projectiles that basically stop working after first hit, you can set momentumTransferFraction to 1 to let the bullet physically push the object (by applying all its momentum to the first hit object) without the bullet being affected itself.
For some reason, you cannot disable default collision detection just for one object. This means that if you are so (un-)lucky and a collision happens to be registered by Unity's default collision checks, you might have OnTriggerEnter2D fire multiple times on the same object, or (if collider is not a trigger) exert a force on hit targets (in addition to the one exerted by this script). However, since that would be somewhat random and very inconsistent, in addition to turning on IsTrigger on your projectile's collider, I recommend using a custom message name to handle projectile impacts. This way, collisions that are randomly detected by default collision detection would not have any unintended side effects [Remember that default collision detection being inconsistent for these sorts of objects is the actual reason why you add this script]. FYI: As of Unity 5, the only two ways to prevent default collision detection are IgnoreCollision and IgnoreLayerCollision.
Code
using UnityEngine;
using System.Collections;
using System.Linq;
/// <summary>
/// 2D adaption of the famous DontGoThroughThings component (http://wiki.unity3d.com/index.php?title=DontGoThroughThings).
/// Uses raycasting to trigger OnTriggerEnter2D events when hitting something.
/// </summary>
/// <see cref="http://stackoverflow.com/a/29564394/2228771"/>
public class ProjectileCollisionTrigger2D : MonoBehaviour {
public enum TriggerTarget {
None = 0,
Self = 1,
Other = 2,
Both = 3
}
/// <summary>
/// The layers that can be hit by this object.
/// Defaults to "Everything" (-1).
/// </summary>
public LayerMask hitLayers = -1;
/// <summary>
/// The name of the message to be sent on hit.
/// You generally want to change this, especially if you want to let the projectile apply a force (`momentumTransferFraction` greater 0).
/// If you do not change this, the physics engine (when it happens to pick up the collision)
/// will send an extra message, prior to this component being able to. This might cause errors or unexpected behavior.
/// </summary>
public string MessageName = "OnTriggerEnter2D";
/// <summary>
/// Where to send the hit event message to.
/// </summary>
public TriggerTarget triggerTarget = TriggerTarget.Both;
/// <summary>
/// How much of momentum is transfered upon impact.
/// If set to 0, no force is applied.
/// If set to 1, the entire momentum of this object is transfered upon the first collider and this object stops dead.
/// If set to anything in between, this object will lose some velocity and transfer the corresponding momentum onto every collided object.
/// </summary>
public float momentumTransferFraction = 0;
private float minimumExtent;
private float sqrMinimumExtent;
private Vector2 previousPosition;
private Rigidbody2D myRigidbody;
private Collider2D myCollider;
//initialize values
void Awake()
{
myRigidbody = GetComponent<Rigidbody2D>();
myCollider = GetComponents<Collider2D> ().FirstOrDefault();
if (myCollider == null || myRigidbody == null) {
Debug.LogError("ProjectileCollisionTrigger2D is missing Collider2D or Rigidbody2D component", this);
enabled = false;
return;
}
previousPosition = myRigidbody.transform.position;
minimumExtent = Mathf.Min(myCollider.bounds.extents.x, myCollider.bounds.extents.y);
sqrMinimumExtent = minimumExtent * minimumExtent;
}
void FixedUpdate()
{
//have we moved more than our minimum extent?
var origPosition = transform.position;
Vector2 movementThisStep = (Vector2)transform.position - previousPosition;
float movementSqrMagnitude = movementThisStep.sqrMagnitude;
if (movementSqrMagnitude > sqrMinimumExtent) {
float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);
//check for obstructions we might have missed
RaycastHit2D[] hitsInfo = Physics2D.RaycastAll(previousPosition, movementThisStep, movementMagnitude, hitLayers.value);
//Going backward because we want to look at the first collisions first. Because we want to destroy the once that are closer to previous position
for (int i = 0; i < hitsInfo.Length; ++i) {
var hitInfo = hitsInfo[i];
if (hitInfo && hitInfo.collider != myCollider) {
// apply force
if (hitInfo.rigidbody && momentumTransferFraction != 0) {
// When using impulse mode, the force argument is actually the amount of instantaneous momentum transfered.
// Quick physics refresher: F = dp / dt = m * dv / dt
// Note: dt is the amount of time traveled (which is the time of the current frame and is taken care of internally, when using impulse mode)
// For more info, go here: http://forum.unity3d.com/threads/rigidbody2d-forcemode-impulse.213397/
var dv = myRigidbody.velocity;
var m = myRigidbody.mass;
var dp = dv * m;
var impulse = momentumTransferFraction * dp;
hitInfo.rigidbody.AddForceAtPosition(impulse, hitInfo.point, ForceMode2D.Impulse);
if (momentumTransferFraction < 1) {
// also apply force to self (in opposite direction)
var impulse2 = (1-momentumTransferFraction) * dp;
hitInfo.rigidbody.AddForceAtPosition(-impulse2, hitInfo.point, ForceMode2D.Impulse);
}
}
// move this object to point of collision
transform.position = hitInfo.point;
// send hit messages
if (((int)triggerTarget & (int)TriggerTarget.Other) != 0 && hitInfo.collider.isTrigger) {
hitInfo.collider.SendMessage(MessageName, myCollider, SendMessageOptions.DontRequireReceiver);
}
if (((int)triggerTarget & (int)TriggerTarget.Self) != 0) {
SendMessage(MessageName, hitInfo.collider, SendMessageOptions.DontRequireReceiver);
}
}
}
}
previousPosition = transform.position = origPosition;
}
}
Here is the 2D version I rewrote of this script (for Unity 4.6):
using UnityEngine;
using System.Collections;
public class DontGoThroughThings : MonoBehaviour
{
public delegate void CollidedDelegate(Collider2D collider);
public event CollidedDelegate Collided;
public LayerMask layerMask; //make sure we aren't in this layer
public float skinWidth = 0.1f; //probably doesn't need to be changed
private float minimumExtent;
private float partialExtent;
private float sqrMinimumExtent;
private Vector2 previousPosition;
private Rigidbody2D myRigidbody;
//initialize values
void Awake()
{
myRigidbody = rigidbody2D;
previousPosition = myRigidbody.transform.position;
minimumExtent = Mathf.Min(BoundsOf(collider2D).extents.x, BoundsOf(collider2D).extents.y);
partialExtent = minimumExtent * (1.0f - skinWidth);
sqrMinimumExtent = minimumExtent * minimumExtent;
}
void FixedUpdate()
{
//have we moved more than our minimum extent?
Vector2 movementThisStep = (Vector2)myRigidbody.transform.position - previousPosition;
float movementSqrMagnitude = movementThisStep.sqrMagnitude;
if (movementSqrMagnitude > sqrMinimumExtent)
{
float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);
//check for obstructions we might have missed
RaycastHit2D[] hitsInfo = Physics2D.RaycastAll(previousPosition, movementThisStep, movementMagnitude, layerMask.value);
//Going backward because we want to look at the first collisions first. Because we want to destroy the once that are closer to previous position
for (int i = hitsInfo.Length-1; i >= 0; i--)
{
var hitInfo = hitsInfo[i];
if (hitInfo && hitInfo.rigidbody != rigidbody2D)
{
if (Collided != null)
{
Collided(hitInfo.collider);
}
}
}
}
previousPosition = myRigidbody.transform.position;
}
// compute bounds in local space
public static Bounds BoundsOf(Collider2D collider) {
var bounds = new Bounds();
var bc = collider as BoxCollider2D;
if (bc) {
var ext = bc.size * 0.5f;
bounds.Encapsulate(new Vector3(-ext.x, -ext.y, 0f));
bounds.Encapsulate(new Vector3(ext.x, ext.y, 0f));
return bounds;
}
var cc = collider as CircleCollider2D;
if (cc) {
var r = cc.radius;
bounds.Encapsulate(new Vector3(-r, -r, 0f));
bounds.Encapsulate(new Vector3(r, r, 0f));
return bounds;
}
// others :P
//Debug.LogWarning("Unknown type "+bounds);
return bounds;
}
// return bounds in world space
public static Bounds BoundsColliders(GameObject obj) {
var bounds = new Bounds(obj.transform.position, Vector3.zero);
var colliders = obj.GetComponentsInChildren<Collider2D>();
foreach(var c in colliders) {
var blocal = BoundsOf(c);
var t = c.transform;
var max = t.TransformPoint(blocal.max);
bounds.Encapsulate(max);
var min = t.TransformPoint(blocal.min);
bounds.Encapsulate(min);
}
return bounds;
}
}
Please let me know if it works for you.
Thanks,
Lidan
Here's my code;
I've looked it up online, but still unable to fix it.
Please show me a fixed version of the code, thank you so much! I've been staring at the screen for half 'n' hour and still can't figure this out!
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D car1Texture;
Vector2 car1Position = new Vector2(200f, 100f);
Texture2D background;
Rectangle mainFrame;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
//Change the resolution to 800x600
graphics.PreferredBackBufferWidth = 1000;
graphics.PreferredBackBufferHeight = 800;
graphics.ApplyChanges();
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Load the background content.
background = Content.Load<Texture2D>("roadscan");
// Set the rectangle parameters
mainFrame = new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
spriteBatch = new SpriteBatch(GraphicsDevice);
car1Texture = Content.Load<Texture2D>("car1");
}
// TODO: use this.Content to load your game content here
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
KeyboardState keyboard = Keyboard.GetState();
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
if (keyboard.IsKeyDown(Keys.Left) || gamePad.DPad.Left == ButtonState.Pressed)
{
ballPosition.X -= 3f;
}
if (keyboard.IsKeyDown(Keys.Right) || gamePad.DPad.Right == ButtonState.Pressed)
{
ballPosition.X += 3f;
}
if (keyboard.IsKeyDown(Keys.Up) || gamePad.DPad.Right == ButtonState.Pressed)
{
ballPosition.Y += 3f;
}
if (keyboard.IsKeyDown(Keys.Down) || gamePad.DPad.Right == ButtonState.Pressed)
{
ballPosition.Y -= 3f;
}
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// Draw the background.
// Start building the sprite.
spriteBatch.Begin();
// Draw the background.
spriteBatch.Draw(background, mainFrame, Color.White);
// End building the sprite.
spriteBatch.End();
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}
Remove the extra brace above the UnloadContent method
Right here:
// TODO: use this.Content to load your game content here
}<-------Delete this guy!!!
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
You've got one to many closing curly braces.
protected override void LoadContent()
{
// snip
} //close method
// TODO: use this.Content to load your game content here
} //close class
You accidentally close the class half way through, just removing the curly brace will fix things. This is common when you haven't shrunk or removed the default comment blocks as they tend to get in the way after you've learnt what all the basic methods do.
This has been really bugging me and I can't seem to ask the right question while searching for a solution! The problem is simple enough: Why can I not attach an event to a programmatically created MovieClip in Actionscript 3? This has got to be something basic I am missing, right?
package {
//imports
import flash.display.*;
import flash.events.*;
//document class
public class Document extends MovieClip {
//variables
public var utilities:Object;
public var test:MovieClip;
//constructor
public function Document()
{
//instantiate the utilities object - this will contain some
//simple functions for setting dimensions and background color of
//a display object.
this.utilities = new Object();
//utility function: set dimensions.
this.utilities.setDimensions = function(what:*, width:uint, height:uint)
{
what.graphics.drawRect(0, 0, width, height);
}
//utility function: set opaque background color.
this.utilities.setBgColor = function(what:DisplayObject, color:uint)
{
what.cacheAsBitmap = true;
what.opaqueBackground = color;
}
//create/add a test movie clip.
test = new MovieClip();
//set dimensions and background color of the test movie clip.
this.utilities.setDimensions(test, 100, 100);
this.utilities.setBgColor(test, 0xff0000);
//add test movie clip to document class.
this.addChild(test);
//add a click event to the test movie clip.
test.addEventListener(MouseEvent.CLICK, onClick);
}
//click event handler
private function onClick(evt:Event):void
{
trace('click');
}
}
}
//update based on Casey Yee's answer
//the set dimension function needs to contain a fill to be clickable
//a fill is added with alpha set to zero to maintain transparency.
//utility function: set dimensions
this.utilities.setDimensions = function(what:*, width:uint, height:uint)
{
what.graphics.beginFill(0x000000, 0);
what.graphics.drawRect(0, 0, width, height);
what.graphics.endFill();
}
test is still a empty movie clip, try adding drawing something into it:
test = new MovieClip();
test.graphics.beginFill(0xFF0000);
test.graphics.drawRect(0,0,100,100);
test.graphics.endFill();