Custom Icon on ScriptableObject on Unity3d - c#

I doing some items for my game make the development easier, I wish to add custom icons to my items, which are ScriptableObjects, how can I do this? I know the trick of put the icon on Gizmo folder with the same name as the script, but the icons should be different for different items which have the same script

You can add a RawImage field to your scriptable object.
[SerializeField] RawImage imageIcon;
Edit to answer your additional question:
Create the new object with your scriptable object
Click on the newly created object
View inspector and click the icon:
Click Other
Pick your image.
Additionally you can check out this asset package and create icons based on the data: https://assetstore.unity.com/packages/tools/gui/asseticons-100547

Create Field "icon" in your ScriptableObject sub class;
Create Custom Editor for your ScriptableObject sub class;
Create Target property in editor
public StateMachine Target {
get {
return this.target as StateMachine;
}
}
override this method
public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
Texture2D newIcon = new Texture2D(width, height);
if (Target.icon != null) {
EditorUtility.CopySerialized(Target.icon, newIcon);
return newIcon;
} else {
Texture2D defaultCustomIcon = AssetDatabase.LoadAssetAtPath("Assets/WinxProduction/Editor/Editor Default Resources/StateMachine Icon.png", typeof(Texture2D)) as Texture2D;
if (defaultCustomIcon != null) {
EditorUtility.CopySerialized(defaultCustomIcon, newIcon);
Target.icon=newIcon;
AssetDatabase.AddObjectToAsset(newIcon,Target);
EditorUtility.SetDirty(Target);
return newIcon;
}
}
return base.RenderStaticPreview(assetPath, subAssets, width, height);
}
First time when Unity Loads will use default Icon and save for all custom sub class ScriptableObjects. If you want to have different icons then default you would create some function like this:
protected void OnClickBrowseForNewIcon() {
string path=EditorUtility.OpenFilePanelWithFilters("Select icon","Assets",new string[]{"Icon files", "png",});
if(!string.IsNullOrEmpty(path)){
Target.icon=AssetDatabase.LoadAssetAtPath(AssetDatabaseUtility.AbsoluteUrlToAssets(path));
EditorUtility.SetDirty(Target);
}
}
Relative to absolute
public static class AssetDatabaseUtility
{
public static string AbsoluteUrlToAssets(string absoluteUrl)
{
Uri fullPath = new Uri (absoluteUrl, UriKind.Absolute);
Uri relRoot = new Uri (Application.dataPath, UriKind.Absolute);
return relRoot.MakeRelativeUri (fullPath).ToString ();
}
}

Related

How to add custom images in a entry (used for a chat) in Xamarin forms macos?

I am trying to create an entry that supports the addition of both text but also images (custom ones). Right now i am trying to achieve this in macOS.
What I have gone ahead and done so far is to create a custom control, where i have a List of images, that i add to if a new image is added to the entry. Once added, the idea is for it to go on the right side of the last text (or image).
Control:
public class ChatEntry : Entry
{
public ChatEntry()
{
this.HeightRequest = 50;
}
public static readonly BindableProperty ImageProperty =
BindableProperty.Create(nameof(Images), typeof(List<string>), typeof(ChatEntry), string.Empty);
public static readonly BindableProperty LineColorProperty =
BindableProperty.Create(nameof(LineColor), typeof(Xamarin.Forms.Color), typeof(ChatEntry), Color.White);
public static readonly BindableProperty ImageHeightProperty =
BindableProperty.Create(nameof(ImageHeight), typeof(int), typeof(ChatEntry), 40);
public static readonly BindableProperty ImageWidthProperty =
BindableProperty.Create(nameof(ImageWidth), typeof(int), typeof(ChatEntry), 40);
public Color LineColor
{
get { return (Color)GetValue(LineColorProperty); }
set { SetValue(LineColorProperty, value); }
}
public int ImageWidth
{
get { return (int)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public int ImageHeight
{
get { return (int)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public List<string> Images
{
get { return (List<string>)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
}
MacOS renderer:
[assembly: ExportRenderer(typeof(ChatEntry), typeof(ChatEntryRenderer))]
namespace Project.MacOS.Renderers
{
public class ChatEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || e.NewElement == null)
return;
var element = (ChatEntry)this.Element;
var textField = this.Control;
if (element.Images != null)
{
// TODO : Logic GetImageView
}
CALayer bottomBorder = new CALayer
{
Frame = new CGRect(0.0f, element.HeightRequest - 1, this.Frame.Width, 1.0f),
BorderWidth = 2.0f,
BorderColor = element.LineColor.ToCGColor()
};
textField.Layer.AddSublayer(bottomBorder);
textField.Layer.MasksToBounds = true;
}
private NSView GetImageView(string imagePath, int height, int width)
{
var uiImageView = new NSImageView()
{
Frame = new RectangleF(0, 0, width, height)
};
uiImageView.Image = NSImage.ImageNamed(imagePath);
NSView objLeftView = new NSView(new System.Drawing.Rectangle(0, 0, width + 10, height));
objLeftView.AddSubview(uiImageView);
return objLeftView;
}
}
}
Issue i have now is to bind this all together. I have created a GetImageView where I can get in return a NSView, but how i incorporate this so that it places on the right side of the prior character (or image), i am unsure and would need some guidance.
Before everything, I'd like to say that Xamarin for MacOS is still in preview, for Entry, there's still some features that pending development and may affect your app, you should consider and test carefully if you're gonna release your app. You can find status for entry here
If I understood your problem correctly, you're trying to create a custom entry that can put both texts and images inside like this:
text[IMG1]abc[IMG2]...
and here's something that might give some ideas: how to implement a label with a image in the center.
It's leveraging NSTextAttachment to hold the image and NSMutableAttributedString]4 to hold the entire string in entry. The difference is that, it only have one image, but you're going to have several images in the entry, so you'll probably need a dictionary to map the placeholder in the string with the actual image.

Adding UIImageView to UIAlertController

Goal: add an image above an UIAlertController's Title label by subclassing UIAlertController and adding new line characters, \n, to the title string to make space for the UIImageView
Desire
Current
As one can see, able to add the image to the UIAlertController successfully but the image is not being spacing/placed above the Title. It appears to be adding to the center of the alert. How to space the image correctly above the UIAlertController title?
Current code:
namespace XamarinFormsApp1.Extensions
{
public class AlertController : UIAlertController
{
private string originalTitle;
private string spaceAdjustedTitle;
private UIImageView imageView = null;
private CoreGraphics.CGSize previousImgViewSize
= CoreGraphics.CGSize.Empty;
public override UIAlertControllerStyle PreferredStyle
{
get
{
return UIAlertControllerStyle.Alert;
}
}
public override string Title
{
get
{
return originalTitle;
}
set
{
if (Title != spaceAdjustedTitle ||
string.IsNullOrEmpty(Title) ||
string.IsNullOrEmpty(spaceAdjustedTitle))
{
originalTitle = value;
}
}
}
public void setTitleImage(UIImage image)
{
if (this.imageView == null)
{
UIImageView imageView = new UIImageView(image);
this.View.AddSubview(imageView);
this.imageView = imageView;
return;
}
imageView.Image = image;
}
public override void ViewDidLayoutSubviews()
{
if (imageView == null)
{
base.ViewDidLayoutSubviews();
return;
}
// Adjust title if image size has changed
if (previousImgViewSize != imageView.Bounds.Size)
{
previousImgViewSize = imageView.Bounds.Size;
adjustTitle(imageView);
}
// Position `imageView`
var linesCount = newLinesCount(imageView);
var padding = Constants.Padding(PreferredStyle);
var x = View.Bounds.Width / 2.0;
var y = padding + linesCount * lineHeight / 2.0;
CoreGraphics.CGPoint cgPoint = imageView.Center;
cgPoint.X = (nfloat)x;
cgPoint.Y = (nfloat)y;
imageView.Center = cgPoint;
base.ViewDidLayoutSubviews();
}
private void adjustTitle(UIImageView imageView)
{
var linesCount = (int)newLinesCount(imageView);
var lines = Enumerable
.Range(1, linesCount)
.Select(i => "\n")
.Aggregate((c, n) => $"{c}{n}");
spaceAdjustedTitle = lines + (originalTitle ?? "");
Title = spaceAdjustedTitle;
}
private double newLinesCount(UIImageView imageView)
{
return Math.Ceiling(
imageView.Bounds.Height / lineHeight);
}
private float lineHeight
{
get
{
UIFontTextStyle style = this.PreferredStyle
== UIAlertControllerStyle.Alert
? UIFontTextStyle.Headline
: UIFontTextStyle.Callout;
return (float)UIFont
.GetPreferredFontForTextStyle(style)
.PointSize;
}
}
struct Constants
{
static float paddingAlert = 22;
static float paddingSheet = 11;
public static float Padding(UIAlertControllerStyle style)
{
return style == UIAlertControllerStyle.Alert
? Constants.paddingAlert
: Constants.paddingSheet;
}
}
}
}
Note: Credit to #stringCode for image and swift solution, see.
UIAlertViewController is not meant to be subclassed.
An extract from the documentation says:
You could still get the UI you desire by using a UIViewController with transparency on the View and a subview with the layout you desire.
You would need to also set these two properties: ModalTransitionStyle and ModalPresentationStyle to UIModalTransitionStyle.CrossDissolve and UIModalPresentationStyle.OverCurrentContext respectively if you want your custom UIAlertController to behaves the same as a UIAlertController
Update:
This is what I meant you could do:
In the Main.Storyboard drop a UIViewController and update the design as you wish. Following the image you posted above I created the UI as seen below:
That's an Image, 2 UILabels for the Title and Message and 3 buttons for the 3 different actions (Default, Destroy, Cancel). All these controls are inside a UIView with White background. For the example I called it ContentView
Adding the 3 button on the UI seems to be the easiest way to work with this and then hide/show them when you are about to present your alert. You could also create the buttons on the fly based on the actions you wanna show. This is up to you.
Create a ViewController Class, I called it NiceAlertController, and assign it to the ViewController in the Storyboard. Also, make sure to create back properties (Outlets) for all the UIControls (Label, Button, Image, etc) so you can access it from the ViewController class.
Here more information about how to work with iOS Storyboard on the designer
Now in your class you will need to add the code to make it work.
In your class to make the view transparent you will need to add this to your ViewDidLoad method:
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.2f);
this.View.Opaque = false;
}
Also, we could mimic the way UIAlertControllers are created and create our method like that one:
public static NiceAlertController Create(string title, string message, UIImage image = null)
{
//New instance of your ViewController UI
var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
var alertController = storyboard.InstantiateViewController(nameof(NiceAlertController)) as NiceAlertController;
//Using the same transition and presentation style as the UIAlertViewController
alertController.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
alertController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
//This assumes your properties are called Title, Message and Image
alertController.Title = title;
alertController.Message = message;
alertController.Image = image;
return alertController;
}
The 3 properties used above (Title, Message and Image) looks like this:
public new string Title { get; private set; }
public string Message { get; private set; }
public UIImage Image { get; private set; }
Why these properties? because by the time you create the class the Controls on the view are not yet available. They will be available only after the View is loaded. This is why we will need to add other changes like the one below.
Here we are setting the values to the Controls on the UI
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.titleLabel.Text = Title;
this.messageLabel.Text = Message;
//If you don't set an image while Create, it will use the default image you set on the Designer.
if (Image != null)
{
this.imageView.Image = Image;
}
}
Now from any other ViewController you can call this ViewController as you would call an AlertViewController:
private void ShowMyAlertController()
{
var alert = NiceAlertController.Create("Hello", "My nice alert controller");
this.PresentViewController(alert, true, null);
}
And it should look like this:
To handle the Actions (What to do when the buttons are tapped) you could create specific methods like:
public void AddDefaultAction(string title, Action action)
{
//Logic here
}
public void AddCancelAction(string title, Action action)
{
//Logic here
}
public void AddDestructiveAction(string title, Action action)
{
//Logic here
}
Hope this gives you the idea of how to create custom UIViewcontroller and make it look like a UIAlertViewController.
Hope this helps.-

Unity Component is referencing the same gameObject instead of itself

I am having this problem where when I instantiate multiple objects at once, the gameObject references the same instance on all objects whereas they should instead reference the gameObject that component is attached to.
So, I have this MonoBehaviour, which is the main part of my code. When the awake function runs it should create an instance of each BootstrapMacro so I don't overwrite the data in the .asset file (Which happens without this part).
Then in the new instance, I set a reference to the current Bootstrap component.
public class Bootstrap : MonoBehaviour {
[SerializeField] List<BootstrapMacro> macros;
public List<BootstrapMacro> runtimeMacros = new List<BootstrapMacro>();
void Awake() {
// Create a runtime version of the macro so to not overwrite the original asset file
macros.ForEach(macro => {
if (macro != null) {
var mac = Instantiate(macro);
mac.events.ForEach(evt => {
evt.bootstrap = this;
evt.actions.ForEach(act => { act.bootstrap = this; });
});
runtimeMacros.Add(mac);
}
});
RunMacro(e => e.OnObjectAwake());
}
void Start() { RunMacro(e => e.OnObjectStart()); }
void RunMacro(System.Action<BootstrapEvent> action) {
runtimeMacros.ForEach(macro => {
if (macro == null) return;
macro.events.ForEach(evt => {
if (!evt.enabled) return;
action.Invoke(evt);
});
});
}
}
My BootstrapMacro file is really basic:
[CreateAssetMenu(fileName = "Bootstrap Macro.asset", menuName = "Boostrap/Macro")]
public class BootstrapMacro : ScriptableObject {
public List<Bootstrap.BootstrapEvent> events = new List<Bootstrap.BootstrapEvent>();
}
Then the event looks like this:
[BootstrapEvent(0)]
public class OnCreate : BootstrapEvent {
public override void OnObjectStart() {
Debug.Log(bootstrap.gameObject.name);
Trigger();
}
}
Which extends this:
public class BootstrapEvent : ScriptableObject {
Bootstrap _bootstrap;
public Bootstrap bootstrap {
get { return _bootstrap; }
set { if (_bootstrap == null) _bootstrap = value; }
}
public virtual void OnObjectStart() { }
}
I am instantiating the objects like this:
var o = Instantiate(_gameObject, _position, Quaternion.identity);
o.name = Random.Range(0, 1000).ToString();
So it is creating the object and giving it a random number. So when it is created I log the name as seen above (code block 3). However, they are all referencing the first object created....
So, what is causing the items to reference the same gameObject?
From the following picture what is happening is the Debug.Log is printing, the object name. As seen there are multiple object names getting printed. A new object name is written to the console every time the previous one is destroyed.
Edit
So, It looks like the issue is with my property. If I remove if(_bootstrap == null), then the code works as expected. If I leave it, it is using the first created item as the reference until it is destroyed then the next create item becomes the reference. Why?
public Bootstrap bootstrap {
get { return _bootstrap; }
set { _bootstrap = value; }
}
var o = Instantiate(_gameObject, _position, Quaternion.identity);
o.name = Random.Range(0, 1000).ToString();
"So, what is causing the items to reference the same gameObject?"
_gameObject is your object reference, all of your var o objects are clones of this. this is the desired behavior with instantiation.
Note that if _gameObject gets destroyed, var o will no longer have a reference, and you will crash. that's why we use prefabs. they cant be removed from the scene so your reference wont go null.

Unity: How to pass self as a parameter of another method c#

let's say I have the following code :
public Text someText;
someText.text = ReturnSomeText();
very simple.
now here is ReturnSomeText() method code :
public string ReturnSomeText()
{
// I want to do some stuff here before returning the text like :
// I want to set fontstyle of someText to Bold
// from outside I can just simply set someText.fontstyle = FontStyle.Bold;
// I have a solution, but I don't like it :
// I can change ReturnSomeText() structure to ReturnSomeText(Text _text)
// Then I can say someText.text = ReturnSomeText(someText);
// but this is not what I want
// because I am planning to call/use this method many times in many occasions,
// I don't want to adjust for every time
// is there any other smart way.
return "Hello World";
}
Thank You
Create UI Text Prefab
Create empty GameObject and attach following script to it. Assign Text prefab to this script (MyText.cs) while you have to attach your own font or whatever you want to use in unity Built-in type. After that you can Instantiate Text prefab dynamically using code and set as well position for describing to 1000 a lot of texts as well I have done for only one prefab.
MyText.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MyText : MonoBehaviour {
class TextProperty
{
public Font font { set; get; }
public string textValue { set; get; }
public FontStyle fontStyle { set; get; }
public int fontSize { set; get; }
}
public Font font;
public Text t;
// Use this for initialization
void Start()
{
var yourT = Instantiate(t, t.transform.position, Quaternion.identity) as Text;
yourT.transform.SetParent(GameObject.Find("Canvas").transform, false);
this.SetTextProps(yourT, this.ReturnSomeText());
}
void SetTextProps(Text yourText, TextProperty tp)
{
yourText.text = tp.textValue;
yourText.font = tp.font;
yourText.fontStyle = tp.fontStyle;
yourText.fontSize = tp.fontSize;
}
TextProperty ReturnSomeText()
{
TextProperty tp = new TextProperty();
tp.font = font;
tp.fontStyle = FontStyle.Bold;
tp.textValue = "Hello World";
tp.fontSize = 20;
return tp;
}
}
Create Many Methods of type ReturnSomeText(...) those will have some parameter for more texts to differentiate or whatever properties you would like to assign them While for-each different Text you must have prefab as well as public Declare Text and assign through inspector with font as while set property to it using ReturnSomeText() type new method.
These things worked well while you are working over dynamic level of objects.

PropertyGrid - get chosen bitmap's file path

I set a PropertyGrid's SelectedObject to an object with a Bitmap property:
public class Obj
{
private Bitmap myimage;
[Category("Main Category")]
[Description("Your favorite image")]
public Bitmap MyImage
{
get { return myimage; }
set
{
myimage = value;
}
}
}
This automatically shows the property in the PropertyGrid with a little button that the user can click on to select the image. How can I get the file path of this image?
(Also, is there any way I can override the dialog box that pops up and put my own one in? I know this is a separate question so I'll probably post it separately.)
How can I get the file path of this image?
Also, is there any way I can override the dialog box that pops up and put my own one in?
To achieve the both one way would be to create a new UITypeEditor:
public class BitmapLocationEditor : UITypeEditor
{
}
I overrided the GetEditStyle, EditValue, GetPaintvalueSupported and PaintValue methods.
// Displays an ellipsis (...) button to start a modal dialog box
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
// Edits the value of the specified object using the editor style indicated by the GetEditStyle method.
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
// Show the dialog we use to open the file.
// You could use a custom one at this point to provide the file path and the image.
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "Image files (*.bmp | *.bmp;";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
Bitmap bitmap = new Bitmap(openFileDialog.FileName);
bitmap.SetPath(openFileDialog.FileName);
return bitmap;
}
}
return value;
}
// Indicates whether the specified context supports painting
// a representation of an object's value within the specified context.
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return true;
}
// Paints a representation of the value of an object using the specified PaintValueEventArgs.
public override void PaintValue(PaintValueEventArgs e)
{
if (e.Value != null)
{
// Get image
Bitmap bmp = (Bitmap)e.Value;
// This rectangle indicates the area of the Properties window to draw a representation of the value within.
Rectangle destRect = e.Bounds;
// Optional to set the default transparent color transparent for this image.
bmp.MakeTransparent();
// Draw image
e.Graphics.DrawImage(bmp, destRect);
}
}
Your Obj class should remain the same, except add an attribute to take advantage of our new BitmapLocationEditor class:
public class Obj
{
private Bitmap myImage;
[Category("Main Category")]
[Description("Your favorite image")]
[Editor(typeof(BitmapLocationEditor), typeof(UITypeEditor))]
public Bitmap MyImage
{
get { return myImage; }
set
{
myImage = value;
}
}
}
The SetPath method used in EditValue method is just an extension method to set the Bitmap.Tag property. You can use later the GetPath method to retrieve the actual path of your image file.
public static class BitmapExtension
{
public static void SetPath(this Bitmap bitmap, string path)
{
bitmap.Tag = path;
}
public static string GetPath(this Bitmap bitmap)
{
return bitmap.Tag as string;
}
}
Article about PropertyGrid

Categories

Resources