I'm trying to minimize the size of a tile object when creating a 2d grid like tilemap. I create an array of short[,] and each [y,x] location corresponds to a id for a tile. To do this I create a class called TileType and have a struct Tile access the information from TileType about itself based on Its id. Like this:
struct Tile
{
short typeId;
public TileType Type
{
get
{
return TileType.Get(typeId);
}
set
{
typeId = value.Id;
}
}
}
class TileType
{
public short Id;
public string Name;
public Texture2D Texture;
public Rectangle TextureSource;
public bool IsObstacle;
static List<TileType> types;
static TileType()
{
types = new List<TileType>();
var type = new TileType();
type.Name = "Dirt";
//initialize here
types.Add(type);
}
public static TileType Get(short id)
{
return types[id];
}
}
I found this by reading a post about how to efficiently store data for a map like this. I didn't write this and its just an example. But my question is how would I draw a tile onto the screen using this method? I would set up a way that the Texture would correspond to a source rectangle(TextureSource) in a tile atlas. But I dont understand how I would actually draw this. IE draw(Tile.Type.Id)? But Id is just a short.
First of all, you should fix a bug in initialization - when you create a type you should set an identifier in it. Like this:
var type = new TileType();
type.Id = 0; // This is unique identifier that we are using in Get method.
type.Name = "Dirt";
type.Texture = new Texture2D(...); //Here you assign your texture for Dirt tile
//Initialize position and size for your texture.
type.TextureSource = new Rectangle(dirtStartX, dirtStartY, dirtWidth, dirtHeight);
types.Add(type);
type = new TileType();
type.Id = 0; // This is unique identifier that we are using in Get method.
type.Name = "Water";
type.Texture = new Texture2D(...); //Here you assign your texture for Dirt tile
//Initialize position and size for your texture.
type.TextureSource = new Rectangle(waterStartX, waterStartY, waterWidth, waterHeight);
types.Add(type);
After this you can use Get by identifier method.
I'll explain the main idea of drawing all tiles in the screen (This is not a working code but it shows what you should do. Its simple:)):
for (int id=0; id<TileTypeCount; id++)
{
TileType tileType = TileType.Get(id); //Get tile by its identifier.
//Now we have current texture and rectangle (position). Draw it
DrawRectangleWithTexture(tileType.Rectangle, tileType.Texture);
}
The implementation of DrawRectangleWithTexture depends on what developer environment do you use. Anyway, in this function you have all information to draw your image:
Rectangle is using for storing information about position and size of your image.
Texture is just a picture that you should draw.
Related
How can I change one material in an array?
I've imported a Daz figure into unity, all of the materials are stored in an array for the figure.
I'm trying to change the texture of the Irises with the click of a button.
The code below does change the material but only on the first material in the array. I've attempted to alter the code to change the Irises, but I have been unable to.
{
//Component
private Renderer _rendereyes;
//Game Object
public GameObject eyes;
//etc
public Object[] texEyes;
public int texID;
// Start is called before the first frame update
void Start()
{
//Get Component
_rendereyes = eyes.GetComponent<Renderer>();
//Change eye tex
string texPath = "Textures";
texEyes = Resources.LoadAll(texPath, typeof(Texture2D));
}
public void SetEyeTexture()
{
if (texID < texEyes.Length - 1)
{
texID++;
}
else
{
texID = 0;
}
_rendereyes.material.SetTexture("_DiffuseMap",(Texture2D)texEyes[texID]);
}
Here is the 2nd iteration of the code which is my attempt to alter the Irises texture.
{
//Component
private Renderer[] _rendereyes;
//Game Object
public GameObject eyes;
//etc
public Object[] texEyes;
public int texID;
// Start is called before the first frame update
void Start()
{
//Get Component
_rendereyes = eyes.GetComponent<Renderer[]>();
//Change eye tex
string texPath = "Textures";
texEyes = Resources.LoadAll(texPath, typeof(Texture2D));
}
public void SetEyeTexture()
{
if (texID < texEyes.Length - 1)
{
texID++;
}
else
{
texID = 0;
}
_rendereyes[15].material.SetTexture("_DiffuseMap",(Texture2D)texEyes[texID]);
}
What is the best way to change the Irises texture?
First of all in general avoid using Resources at all. (See Best practices -> Resources)
Don't use it!
Then if you use it and in general I would be more specific and do
public Texture2D[] texEyes;
and then
texEyes = Resources.LoadAll<Texture2D>(texPath);
or rather simply drag and drop them into the slots via the Inspector
And finally
GetComponent<Renderer[]>
makes no sense. You always want to either get a single component or use GetComponents.
However, it sounds like you actually have only one single object with one single Renderer and want to stick to
_rendereyes = eyes.GetComponent<Renderer>();
or directly make it
[SerializeField] private Renderer _rendereyes;
and drag the object into the slot in the Inspector.
And then rather access the according index from Renderer.materials.
And then you have a little logical mistake in your SetEyeTexture method.
You do
if (texID < texEyes.Length - 1)
{
texID++;
}
which might result in texID = texEyes.Length which will be out o bounds since in c# all indices go from 0 to array.Length - 1.
Your code should rather look like
public void SetEyeTexture()
{
// Simple modulo trick to get a wrap around index
texID = (++texID) % texEyes.Length;
// Note that according to your screenshot the Material "Irises" is at index 14 not 15!
_rendereyes.materials[14].SetTexture("_DiffuseMap", texEyes[texID]);
}
I have these constants:
private const double DOWN_LEFTX = 31.327089;
private const double DOWN_LEFTY = 34.848469;
and I would like to place a symbol at those exact coordinates.
public MainPage()
{
InitializeComponent();
Initialize_map();
}
private void Initialize_map()
{
// Create a map with 'Imagery with Labels' basemap and an initial location
Map myMap = new Map(BasemapType.ImageryWithLabels, 32.560407, 35.608784, 16);
MyMapView.Map = myMap;
// Set the map views graphics overlay to the created graphics overlay.
MyMapView.GraphicsOverlays.Add(theGraphicsOverlays);
theGraphicCollection.Add(new Graphic(new MapPoint(DOWN_LEFTX, DOWN_LEFTY), new SimpleMarkerSymbol(SimpleMarkerSymbolStyle.X, System.Drawing.Color.Red, 14)));
}
I don't know why it doesn't work. My initial thought was that the object MapPoint is using different coordinate format, any help would be appreciated.
public Viewport view;
public Camera(Viewport newView)
{
view = newView;
zoom = 2;
rotation = 0;
}
Sorry if this seems very basic but I can't figure it out for the life of me. I want to create an Object called 'camera' in my Game class, but I don't know what I need to input as the Viewport value.
Right now, in my Game class, I only have this:
Camera camera = new Camera(//don't know what goes here);
You will need to pass it graphics.GraphicsDevice.Viewport as the parameter.
This assumes you are creating the instance of Camera in Game1.cs. Which you should be doing, since this effects the entire game.
Creating a new instance is redundant, and may not fit the target device.
Your constructor of the "Camera" class needs an "Viewport" object. Not more not less!
Viewport vp = new Viewport();
Camera cam = new Camera(vp);
I don't know the Viewport class... Possible is also...
a)
Viewport vp = new Viewport(param1, param2,...);
b)
Viewport vp = new Viewport() { Property1 = param1, Property2 = param2, ...};
c)
Or do it instant Camera cam = new Camera(new Viewport {Property1 = param1, Property2 = param2, ...}
I am currently following this tutorial for adding a polygon to a map. I need to be able to add multiple polygons to my map, so I have slightly altered the code so that I can use addOverlays which takes in an array of IMKOverlay objects instead of one addOverlay which just takes in a single IMKOverlay object.
This doesn't work however... It only draws the first polygon on the map!
void addPolygonsToMap()
{
overlayList = new List<IMKOverlay>();
for (int i = 0; i < polygons.Count; i++)
{
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[polygons[i].Count];
int index=0;
foreach (var position in polygons[i])
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var blockOverlay = MKPolygon.FromCoordinates(coords);
overlayList.Add(blockOverlay);
}
IMKOverlay[] imko = overlayList.ToArray();
nativeMap.AddOverlays(imko);
}
In this discussion, it would appear that I have to call a new instance of MKPolygonRenderer each time I need to add another polygon to my map, but I'm unsure how this example translates to my code. Here is my MKPolygonRenderer function:
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (polygonRenderer == null && !Equals(overlayWrapper, null)) {
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
polygonRenderer = new MKPolygonRenderer(overlay as MKPolygon) {
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
}
return polygonRenderer;
}
Create a new renderer instance each time OverlayRenderer is called, there is no need to cache the renderer in a class level variable as the MKMapView will cache the renderers as needed.
Subclass MKMapViewDelegate:
class MyMapDelegate : MKMapViewDelegate
{
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
switch (overlay)
{
case MKPolygon polygon:
var prenderer = new MKPolygonRenderer(polygon)
{
FillColor = UIColor.Red,
StrokeColor = UIColor.Blue,
Alpha = 0.4f,
LineWidth = 9
};
return prenderer;
default:
throw new Exception($"Not supported: {overlay.GetType()}");
}
}
}
Instance and assign the delegate to your map:
mapDelegate = new MyMapDelegate();
map.Delegate = mapDelegate;
Note: Store the instance of your MyMapDelegate in a class level variable as you do not want to get GC'd
Update:
MKMapView has two steps involved to display an overlay on its map.
1. Calling `AddOverlay` and `AddOverlays`
First you add overlays to the map that conform to IMKOverlay. There are basic built-in types such as MKCircle, MKPolygon, etc... but you can also design your own overlays; i.e. overlays that define the location of severe weather (lightning, storm clouds, tornados, etc..). These MKOverlays describe the geo-location of the item but not how to draw it.
2. Responding to `OverlayRenderer` requests
When the display area of the map intersects with one of the overlays, the map need to draw it on the screen. The map's delegate (your MKMapViewDelegate subclass) is called to supply a MKOverlayRenderer that defines the drawing routines to paint the overlay on the map.
This drawing involves converting the geo-coordinates of the overlay to local display coordinates (helper methods are available) using Core Graphics routines (UIKit can be used with some limitations). There are basic built-in renderers for MKCircleRenderer, MKPolygonRenderer, etc.. that can be used or you can write your own MKOverlayRenderer subclass.
You could supply a custom way to renderer a MKCircle overlay, maybe a target-style red/white multi-ringed bullseye, instead of the way the default circle renderer draws it, or custom renderers that draw severe storm symbols within the bounds of a MKPolygon to match your custom severe storm overlays.
My Example code:
Since you are using MKPolygon to build your overlays, you can use the MKPolygonRenderer to display them. In my example, I provide a pattern matching switch (C# 6) that returns a semi-transparent Red/Blue MKPolygonRenderer for every MKPolygon that you added to the map (if you added a non-MKPolygon based overlay it will throw an exception).
I was also stuck in this issue and I have found the way to create the sub class of MKPolygon.
I have checked it with my example and it works like a charm. But not sure that Apple may reject my app or not.
public class CvPolyon : MKPolygon
{
public CustomObject BoundaryOption { get; }
public CvPolyon1(MKPolygon polygon, CustomObject boundaryOption)
:base(polygon.Handle)
{
BoundaryOption = boundaryOption;
}
}
We can add polygon on map like this.
var polygon = MKPolygon.FromCoordinates(coordinates);
var overlay = new CvPolyon(polygon, new CustomObject());
mapView.AddOverlay(overlay);
We can recognize our polygon in the class which extends MKMapViewDelegate like this.
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
if (overlay is CvPolyon polygon)
{
var polygonRenderer = new MKPolygonRenderer(polygon)
{
FillColor = polygon.BoundaryOption.AreaColor,
StrokeColor = polygon.BoundaryOption.LineColor,
Alpha = polygon.BoundaryOption.Alpha,
LineWidth = polygon.BoundaryOption.LineWidth
};
if (polygon.BoundaryOption.IsDashedLine)
polygonRenderer.LineDashPattern = new[] { new NSNumber(2), new NSNumber(5) };
return polygonRenderer;
}
return mapView.RendererForOverlay(overlay);
}
I'd like to get a single sprite which GameObject has in a SpriteRenderer component.
Unfortunately, this code returns the whole atlas, but I need to a part of this atlas.
Texture2D thumbnail = GetComponent<SpriteRenderer>().sprite.texture;
There is no native API to get single sprite from the SpriteRenderer and no API to access individual Sprite by name. You can vote for this feature here.
You can make your own API to get single sprite from Atlas located in the Resources folder like the image included in your question.
You can load all the sprites from the Atlas with Resources.LoadAll then store them in a dictionary.A simple function can then be used to access each sprite with the provided name.
A simple Atlas Loader script:
public class AtlasLoader
{
public Dictionary<string, Sprite> spriteDic = new Dictionary<string, Sprite>();
//Creates new Instance only, Manually call the loadSprite function later on
public AtlasLoader()
{
}
//Creates new Instance and Loads the provided sprites
public AtlasLoader(string spriteBaseName)
{
loadSprite(spriteBaseName);
}
//Loads the provided sprites
public void loadSprite(string spriteBaseName)
{
Sprite[] allSprites = Resources.LoadAll<Sprite>(spriteBaseName);
if (allSprites == null || allSprites.Length <= 0)
{
Debug.LogError("The Provided Base-Atlas Sprite `" + spriteBaseName + "` does not exist!");
return;
}
for (int i = 0; i < allSprites.Length; i++)
{
spriteDic.Add(allSprites[i].name, allSprites[i]);
}
}
//Get the provided atlas from the loaded sprites
public Sprite getAtlas(string atlasName)
{
Sprite tempSprite;
if (!spriteDic.TryGetValue(atlasName, out tempSprite))
{
Debug.LogError("The Provided atlas `" + atlasName + "` does not exist!");
return null;
}
return tempSprite;
}
//Returns number of sprites in the Atlas
public int atlasCount()
{
return spriteDic.Count;
}
}
Usage:
With the Example image above, "tile" is the base image and ball, bottom, people, and wallframe are the sprites in the atlas.
void Start()
{
AtlasLoader atlasLoader = new AtlasLoader("tiles");
Debug.Log("Atlas Count: " + atlasLoader.atlasCount());
Sprite ball = atlasLoader.getAtlas("ball");
Sprite bottom = atlasLoader.getAtlas("bottom");
Sprite people = atlasLoader.getAtlas("people");
Sprite wallframe = atlasLoader.getAtlas("wallframe");
}
You could put the image you need in the Resources folder by itself, then use the Resources.Load("spriteName") to get it. If you want to get it as a sprite, you would do the following:
Sprite thumbnail = Resources.Load("spriteName", typeof(Sprite)) as Sprite;
Source: https://forum.unity3d.com/threads/how-to-change-sprite-image-from-script.212307/
Well with the new Unity versions you can do it easily using SpriteAtlas class and GetSprite method:
https://docs.unity3d.com/ScriptReference/U2D.SpriteAtlas.html
So if you are working with the Resources folder you can do:
Resources.Load<SpriteAtlas>("AtlasName")