I'm using SFML for C#. I want to create a BackgroundImage Sprite and then start drawing it with an Agent, represented as a Circle, on top of it like that:
static void Main(string[] args)
{
Window = new RenderWindow(new VideoMode((uint)map.Size.X * 30, (uint)map.Size.Y * 30), map.Name + " - MAZE", Styles.Default);
while (Window.IsOpen)
{
Update();
}
}
static public RenderWindow Window { get; private set; }
static Map map = new Map(string.Format(#"C:\Users\{0}\Desktop\Maze.png", Environment.UserName));
static public void Update()
{
Window.Clear(Color.Blue);
DrawBackground();
DrawAgent();
Window.Display();
}
static void DrawAgent()
{
using (CircleShape tempCircle = new CircleShape
{
FillColor = Color.Cyan,
Radius = 15,
Position = new Vector2f(30, 30),
Origin = new Vector2f(30, 30),
Scale = new Vector2f(.5f, .5f)
})
{
Window.Draw(tempCircle);
}
}
static private Sprite BackgroundImage { get; set; }
static void DrawBackground()
{
if (BackgroundImage == null)
BackgroundImage = GetBackground();
Window.Draw(BackgroundImage);
}
static Sprite GetBackground()
{
RenderTexture render = new RenderTexture((uint)map.Size.X * 30, (uint)map.Size.Y * 30);
foreach (var point in map.Grid.Points)
{
RectangleShape pointShape = new RectangleShape(new Vector2f(30, 30));
switch (point.PointType)
{
case PointType.Walkable:
pointShape.FillColor = Color.White;
break;
case PointType.NotWalkable:
pointShape.FillColor = Color.Black;
break;
case PointType.Start:
pointShape.FillColor = Color.Red;
break;
case PointType.Exit:
pointShape.FillColor = Color.Blue;
break;
}
pointShape.Position = new Vector2f(point.Position.X * 30, point.Position.Y * 30);
render.Draw(pointShape);
}
Sprite result = new Sprite(render.Texture);
result.Origin = new Vector2f(0, result.GetLocalBounds().Height);
result.Scale = new Vector2f(1, -1);
return result;
}
Everything works as intended when I start it, but after a few seconds, around the time when process memory reaches 70MB, BackgroundImage turns into completely white sprite. If I change the type of BackgroundImage and GetBackground() to RenderTexture, return "render" object and then change DrawBackground() function like this
void RenderBackground()
{
if (BackgroundImage == null)
BackgroundImage = GetBackground();
using (Sprite result = new Sprite(BackgroundImage.Texture))
{
result.Origin = new Vector2f(0, result.GetLocalBounds().Height);
result.Scale = new Vector2f(1, -1);
Window.Draw(result);
}
}
then the background sprite doesn't turn white, but storing entire RenderTexture, instead of Sprite and then constantly creating new Sprite objects every time we call RenderBackground() function seems like a bad idea.
Is there any way for GetBackground() function to return a Sprite which won't turn white once the function's local "render" variable is destroyed?
You're not completely off with your assumptions. Simplified, SFML knows two types of resources:
Light resources are small objects that are quick to create and destroy. It's not that bad to just drop them and recreate them later. Typical examples would be Sprite, Sound, Text, and basically most SFML classes.
Heavy resourcces are often big objects or objects requiring file access to create or use. Typical examples would be Image, Texture, SoundBuffer, and Font. You shouldn't recreate these and instead keep them alive while you need them. If they're disposed too early, light resources using them will fail in some way or another.
A sprite's texture turning white is – as you've discovered – a typical sign of the assigned texture being freed/disposed.
There are many different approaches to this, but I'd suggest you create some kind of simple resource manager that will load resources just in time or just return them, if they're loaded already.
I haven't used SFML with C# and I haven't really touched C# for quite a while, but for a simple implementation you'd just have a Dictionary<string, Texture>. When you want to load a texture file like texture.png, you look whether there's a dictionary entry with that key name. If there is, just return it. If there isn't, create the new entry and load the texture, then return it.
I'm out of practice, so please consider this pseudo code!
private Dictionary<string, Texture> mTextureCache; // initialized in constructor
public Texture getTexture(file) {
Texture tex;
if (mTextureCache.TryGetValue(file, out tex))
return tex;
tex = new Texture(file);
mTextureCache.add(file, tex);
return tex;
}
// Somewhere else in your code:
Sprite character = new Sprite(getTexture("myCharacter.png"));
If your heavy resource is a RenderTexture, you just have to ensure that it stays alive as long as it's used (e.g. as a separate member).
It turned out the answer was simpler that I expected. All I had to to was create new Texture object and then make a Sprite out of it. So instead of
Sprite result = new Sprite(render.Texture);
I wrote
Sprite result = new Sprite(new Texture(render.Texture));
Now garbage collector doesn't dispose Sprite's texture
Related
I'm trying to get an image in skiasharp that's left rotated by 90 degrees to be centered and fit perfectly on the canvas. I've tried 2 ways. My own custom way, and another one that seems like a popular solution but maybe I'm not understanding how it works correctly?
My own way.
Here is the code:
SKSurface surf = e.Surface;
SKCanvas canvas = surf.Canvas;
SKSize size = canvasView.CanvasSize;
canvas.Clear();
SKRect rect = SKRect.Create(0.0f, 0.0f, size.Height, size.Width);
canvas.RotateDegrees(85);
canvas.DrawBitmap(m_bm, rect);
"m_bm" is a bitmap that was retrieved in a separate function. That function is:
// Let user take a picture.
var result = await MediaPicker.CapturePhotoAsync(new MediaPickerOptions
{
Title = "Take a picture"
});
// Save stream.
var stream = await result.OpenReadAsync();
// Create the bitmap.
m_bm = SKBitmap.Decode(stream);
// Set to true because the image will be prepared soon.
m_displayedImage = true;
I only put 85 instead of 90 because I wanted to visually see it getting closer but when I do that, it goes off screen. I'm coming from a game programming background so this is normally solved with getting the width of whatever we're working with (like the player in the game) and adding that to the x position, and boom. But with Xamarin, didn't work. That's my own attempt. Then I hit the internet of course to find help, and a different implementation was given to me.
Popular solution.
See here for this popular solution and it's the FIRST answer to this users question. The code I used is SLIGHTLY different because I didn't see the point in returning an image in that users function. Here it is:
// Save stream.
var stream = await result.OpenReadAsync();
using (var bitmap = SKBitmap.Decode(stream))
{
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Clear();
surface.Translate(rotated.Height, rotated.Width);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
}
I'm drawing the bitmap with the canvas and I thought that would work because testing it in other code samples it did exactly that, so I definitely am not rotating properly or something?
The link that #Cheesebaron gave me in the reply to the original post ended up working out. But a new issue arises but I'll google that myself. Here's my own code:
namespace BugApp
{
public partial class MainPage : ContentPage
{
// Save bitmaps for later use.
static SKBitmap m_bm;
static SKBitmap m_editedBm;
// Boolean for displaying the image captured with the camera.
bool m_displayedImage;
public MainPage()
{
// Set to explicit default values to always be in control of the assignments.
m_bm = null;
m_editedBm = null;
// No picture has been taken yet.
m_displayedImage = false;
InitializeComponent();
}
// Assigned to the button in the xaml page.
private async void SnapPicture(Object sender, System.EventArgs e)
{
// Let user take a picture.
var result = await MediaPicker.CapturePhotoAsync(new MediaPickerOptions
{
Title = "Take a picture"
});
// Save stream.
var stream = await result.OpenReadAsync();
// Create the bitmap.
m_bm = SKBitmap.Decode(stream);
// Get the rotated image.
m_editedBm = Rotate();
// Set to true because the image will be prepared soon.
m_displayedImage = true;
}
public static SKBitmap Rotate()
{
using (var bitmap = m_bm)
{
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(bitmap.Width, 0);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
if(m_bm != null && m_displayedImage == true)
{
e.Surface.Canvas.Clear();
// Draw in a new rect space.
e.Surface.Canvas.DrawBitmap(m_editedBm, new SKRect(0.0f, 0.0f, 300.0f, 300.0f));
// ---Testing.
// e.Surface.Canvas.DrawBitmap(m_editedBm, new SKRect(112, 238, 184, 310), new SKRect(0, 0, 9, 9));
// Avoid having this function launch again for now.
m_displayedImage = false;
}
}
}
}
The main portion of code that matters is the rotate function which was this one here: Link.
Thanks to everyone that replied.
I am trying to make a little game coded in c#, the game involves moving enemies.
These enemies are spawned in using the following code, this code is used multiple times to spawn multiple enemies.
private void EventHandler(Action<object, EventArgs> spawnBox)
{
Random randomPlek = new Random();
int xPlek;
xPlek = randomPlek.Next(1000, 1100);
int yPlek;
yPlek = (randomPlek.Next(0, 8)) * 100;
var picture = new PictureBox
{
Name = "pictureBoxLM",
Size = new Size(150, 100),
SizeMode = PictureBoxSizeMode.StretchImage,
BackColor = Color.Transparent,
Location = new Point(xPlek, yPlek),
Image = Leeuwenmier,
};
this.Controls.Add(picture);
}
The problem is that when trying to make them move or collide, Visual Studio can't find the name and gives an error. This is the code i used for collision:
if(PbMier.Bounds.IntersectsWith(pictureBoxLM.Bounds))
{
// some actions
}
How can I call the spawned picturebox in the code without getting an error?
WinForms controls have names, but that doesn't mean you can access them using that name as a C# identifier.
Your PictureBox only has a named reference within EventHandler(), namely picture, but once control leaves that method that reference goes out of scope.
You need to find the controls again, or find another way to reference the generated controls.
So either:
var allPictureBoxes = this.Controls.Find("PictureBoxLM");
foreach (var pictureBox in allPictureBoxes)
{
// ...
}
Or put this on your form:
List<PictureBox> pictureBoxList = new List<PictureBox>();
And then in the EventHandler();
this.Controls.Add(picture);
pictureBoxList.Add(picture);
After which you can use this for your collision detection:
foreach (var pictureBox in pictureBoxList)
{
// ...
}
I'm making a custom editor window and I want to draw EditorGUILayout.FloatField on it. if I write this:
EditorGUILayout.FloatField("change val", someFloatValue);
a label appears in front of the field. But I want it to appears behind the field. Also, I'd like to change simple text with a texture.
What I have now:
What I'd like to achieve:
UPD. I missed important information:
A mouse behavior over a texture must be the same as over a FloatField label, i.e. if I click on the texture and start dragging a cursor - the float value in the field must changes
I couldn't find an option to do it in a simple way. Maybe I miss something.
Is it possible not to create a lot of auxiliary classes for that simple action? If yes, then how?
Just put a label field after the floatfield
EditorGUILayout.BeginHorizontal();
someFloatValue = EditorGUILayout.FloatField(someFloatValue);
EditorGUILayout.LabelField("Change Val")
EditorGUILayout.EndHorizontal();
If you want to put a texture, then just create GUIStyle with the texture you want as background, and use the style for the label
GUIStyle myStyle = new GUIStyle();
myStyle.normal.background = myTexture;
EditorGUILayout.BeginHorizontal();
someFloatValue = EditorGUILayout.FloatField(someFloatValue);
EditorGUILayout.LabelField("", myStyle)
EditorGUILayout.EndHorizontal();
Now, to handle widths, just use GUILayout.Width()
GUIStyle myStyle = new GUIStyle();
myStyle.normal.background = myTexture;
EditorGUILayout.BeginHorizontal();
someFloatValue = EditorGUILayout.FloatField(someFloatValue, GUILayout.Width(150));
EditorGUILayout.LabelField("", myStyle)
EditorGUILayout.EndHorizontal();
It's very possible, but you shouldn't be GUILayout / EditorGUILayout classes. The Layout in the class names means that those variants handle positioning themselves, they each take up a default amount of space and move to the next row, its very conveinient but to do a little bit more advanced stuff, you should use versions of the GUI that take Rect as a position for a draw, so take from GUI/ EditorGUI classes (without Layout in the class name)
I recommend you start by doing a custom PropertyDrawer first, the API for a PropertyDrawer calls for manual element placement, so theres a ton of good examples. Once you start drawing using Rects instead of auto-Layouts there's no limit to how many layers of stuff to paint where, its actually trivial to start painting outside your own inspector (as long as its in the same window)
Well, I've made an UGLY workaround for the field (also for IntField).
If remove all redundant code that I used for content filling - it will be pretty short...
A simplified example looks like this:
using UnityEditor;
using UnityEngine;
public class MainWindow : EditorWindow {
private float floatFieldVal;
private Rect groupFloatFieldRect;
[MenuItem("Examples/Test")]
static void Init() {
MainWindow window = (MainWindow)GetWindow(typeof(MainWindow), false, "My Empty Window");
window.Show();
}
void OnGUI() {
EditorGUILayout.BeginHorizontal(); {
EditorGUILayout.BeginVertical(); {
GUILayout.Button("Button 1");
GUILayout.Button("Button 2");
GUILayout.Button("Button 3");
} EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.Width(300)); {
// never load asset in the loop :)
string assetFullPath = "Assets/Editor/Test.guiskin";
var fakeFieldGUISkin = (GUISkin)AssetDatabase.LoadAssetAtPath(assetFullPath, typeof(GUISkin));
GUIStyle fakeFieldStyle = fakeFieldGUISkin.GetStyle("test");
// place fake floatField right over a real field texture button
Rect test = new Rect(groupFloatFieldRect);
test.position = new Vector2(test.x + groupFloatFieldRect.width - 20, test.y + 3);
test.size = new Vector2(20, 20);
floatFieldVal = EditorGUI.FloatField(test, "fake", floatFieldVal, fakeFieldStyle);
// Draw FloatField And Texture as a Group
Rect groupRect = EditorGUILayout.BeginHorizontal(); {
// never create GUIStyle in the loop :)
GUIStyle floatIconStyle = new GUIStyle(EditorStyles.toolbarButton) {
fixedWidth = 20f,
margin = new RectOffset(0, 0, 0, 0),
padding = new RectOffset(0, 0, 0, 0)
};
floatFieldVal = EditorGUILayout.FloatField("", floatFieldVal);
// It emulates a texture
GUILayout.Label("◀▶", floatIconStyle)
// save group rect in a variable
if (Event.current.type == EventType.Repaint)
groupFloatFieldRect = groupRect;
} EditorGUILayout.EndHorizontal();
} EditorGUILayout.EndVertical();
} EditorGUILayout.EndHorizontal();
}
void OnInspectorUpdate() {
Repaint();
}
}
Of course all magic numbers are just for a quick example. Also guiskin loading and a GUIStyle creating shouldn't be in a loop (in this case in OnGUI). It's for a quick example too.
Test.guiskinis for removing default skin data and regulating other params if needed.
That's how the code above looks:
screenshot of a window.jpg
animated demonstation of fake field.gif
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 am using the background scrolling tutorial on xnadevelopment.com (modified to suit my requirement) to create a vertical scrolling loop for a game on windows phone 7.1. It seems like the background is flickering whenever the next image is drawn. Though I am using a single image for the loop, the flickering occurs even if multiple images are used. I have posted a youtube video showing the flicker that occurs at the top of the screen.
http://youtu.be/Ajdiw2zILq0
Below is the code used to create the loop:
Background class:
private List<string> _road;
private VericalBackgroundLoop _roadLoop;
private readonly Vector2 _roadSpeed = new Vector2(0, 300);
public void LoadContent(ContentManager contentManager)
{
_road = new List<string>
{
"Test\\Road_Bgnd",
"Test\\Road_Bgnd"
};
_roadLoop = new VericalBackgroundLoop();
_roadLoop.Initialize(_road, contentManager, Vector2.Zero, true);
}
public void Update(TimeSpan elapsedTime)
{
_roadLoop.Update(_roadSpeed, elapsedTime);
}
public void Draw(SpriteBatch spriteBatch)
{
_roadLoop.Draw(spriteBatch);
}
Background loop class:
private List<Sprite> _sprites;
private bool _isLoopDirectionTopToBottom;
private Vector2 _loopDirection;
public void Initialize(List<string> spriteNames, ContentManager contentManager, Vector2 loopStartPosition, bool isLoopDirectionTopToBottom)
{
_sprites = new List<Sprite>();
_isLoopDirectionTopToBottom = isLoopDirectionTopToBottom;
_loopDirection = new Vector2(0, -1);
// Build the sprite object's list
foreach (string spriteName in spriteNames)
{
Sprite sprite = new Sprite();
sprite.LoadContent(contentManager, spriteName);
_sprites.Add(sprite);
}
if (_isLoopDirectionTopToBottom)
{
// Set the initial position for the sprite objects
foreach (Sprite currentSprite in _sprites)
{
if (currentSprite == _sprites.First())
{
currentSprite.Position = loopStartPosition;
}
else
{
Sprite prevSprite = GetSpriteAtIndex(_sprites.IndexOf(currentSprite) - 1);
currentSprite.Position = new Vector2(0, prevSprite.Position.Y - prevSprite.Size.Height);
}
}
}
}
public void Update(Vector2 loopSpeed, TimeSpan elapsedTime)
{
if (_isLoopDirectionTopToBottom)
{
foreach (Sprite currentSprite in _sprites)
{
if (currentSprite == _sprites.First())
{
Sprite lastSprite = _sprites.Last();
if (currentSprite.Position.Y > (currentSprite.Size.Height))
{
currentSprite.Position.Y = lastSprite.Position.Y - lastSprite.Size.Height;
}
}
else
{
Sprite prevSprite = GetSpriteAtIndex(_sprites.IndexOf(currentSprite) - 1);
if (currentSprite.Position.Y > (currentSprite.Size.Height))
{
currentSprite.Position.Y = prevSprite.Position.Y - prevSprite.Size.Height;
}
}
// Update the sprite X position with the speed and time
currentSprite.Position -= _loopDirection * loopSpeed * (float)elapsedTime.TotalSeconds;
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
foreach (Sprite sprite in _sprites)
{
sprite.Draw(spriteBatch);
}
}
private Sprite GetSpriteAtIndex(int index)
{
return _sprites[index];
}
I need help in figuring out why the flicker is occurring and why motion seems to be jerky and not smooth (it is a bit better on the device, but jerky nevertheless). IsFixedTimeStep is set to true in the game.cs class. Thank you.
EDIT : Seems like the flicker is not occuring if 3 or more images are used. This could be due to the first image not being placed back into the start position quickly enough. But am still trying to figure out whey the animation is still so jerky :(
You wouldn't be the first to report seeing flickers or stutters. Lots of posts on the App Hub forums about that. (glad you're finding luck using my tutorials/samples by the way!)
Here's an example of someone reporting what you're seeing -> http://forums.create.msdn.com/forums/t/30500.aspx
And here's the best answer I've seen to date from one of the XNA Framework developers ->
http://forums.create.msdn.com/forums/p/9934/53561.aspx#53561
Basically you've stumbled onto "a" solution, but as Shawn's post points out there's a variety of ways to fix the problem, it just depends on what's right for your game.