Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

2 Pages12>
Options
Go to last post Go to first unread
Offline internetfreak  
#1 Posted : Tuesday, May 28, 2013 8:43:35 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Hello everyone,
I'm currently working on something which has to do with tilemaps and I already have functional code which actually render it. Now I only have one problem: Due to the entity system, each Sprite gets immediately added to the entity system but it never gets removed from there to be rendered so with the time I collect many Sprites (over 4-5k) so my FPS obviously drop.
I want to avoid that but I also want a clean solution to do that and here's my problem: How can I achieve the best result?
One thought I already had was to create 3 Sprites (one for each layer, I want to use 3 layers for tiles) which are logically rendered on the same position but with different renderlayers so they seem to overlay themselves.
To have 3 sprites is in my opinion a bit dull, too much sprites. Ealier, when 0.9.5 and lesser were actual, I always fetched a texture loaded with the ContentManager and created a Material2DColored which I then added to the Renderer (I used the Draw() Method since each Material2DColored was able to "draw" itself). Now the Sprite only supports one Image at a time and switching the images on existing sprites is not possible because a Sprite is a class and therefore a reference type so I only change one tile and end up with only one tile due to the fact that the rendering system of delta does not add entites which are already added to the rendering list.

How could I still be able to render my tiles and use as less sprites as possible? I currently store the index of the used tile image to get it from the list of available images and create the sprite with that information and the position of the tile.

I hope it's understandable what I want. One note, I want to stay as clean as possible so I rather want to use things the engine already has instead of deriving engine type to alter their behaviour. Maybe it's possible to clear the rendering list each frame?
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -

Wanna join the discussion?! Login to your forum accountregister a new account. Or Connect via Facebook Twitter Google

Offline elasto  
#2 Posted : Wednesday, May 29, 2013 3:17:03 AM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Hi internetfreak

I'm not sure I follow exactly what you are looking to do but here's a couple of thoughts on what you've said:

Quote:
Due to the entity system, each Sprite gets immediately added to the entity system but it never gets removed from there to be rendered so with the time I collect many Sprites (over 4-5k) so my FPS obviously drop.


Previously Entities were not added to the EntitySystem by default and now they are - this is because probably 95%+ of the time someone creates an Entity they actually want to make use of it straightaway. However, it's still possible to add and remove Entities from the EntitySystem on demand (including at creation) by simply changing the IsActive flag:

var sprite = new Sprite(...) { IsActive = false }; // Creating a sprite that does not render


var sprite = new Sprite(...); // This sprite is rendering
...
sprite.IsActive = false; // We stop this sprite rendering



You should not be seeing any framerate drop over time so long as you are inactivating sprites that are no longer wanted.

---

On a side-note, though it isn't really applicable here, if you wanted the Sprite to stay active in the EntitySystem but not render - let's say it had some internal values it was updating - eg. the Sprite is an enemy creep that has health that is regen'ing - instead of setting it to inactive you could just remove the Render EntityHandler:

sprite.Remove<Sprite.Render>(); // Stops this sprite rendering but all other handlers keep on processing
...
sprite.Add<Sprite.Render>(); // Restarts rendering of this sprite


(Looking at this, I think I might like to change the names of those methods; I think the following reads more natually:

sprite.Stop<Sprite.Render>(); // Stops this sprite rendering but all other handlers keep on processing
...
sprite.Start<Sprite.Render>(); // Restarts rendering of this sprite


Maybe expect to see that changed in the next couple of releases!)

---

Quote:
Now the Sprite only supports one Image at a time and switching the images on existing sprites is not possible because a Sprite is a class and therefore a reference type so I only change one tile and end up with only one tile due to the fact that the rendering system of delta does not add entites which are already added to the rendering list.


I don't entirely follow but let me attempt to answer:

To start with, changing the image on an existing Sprite is possible and easy, it's just:

sprite.Image = content.Load<Image>("New Image");

Yes, you can't add the same entity more than once, but you wouldn't want to. A Sprite can only be in one place at a time; You can't render one Sprite in multiple places in a single frame.

What I think you're getting at is you're worried about having lots of Sprites all using the same Image, worrying that it'll be inefficient - but don't worry, Images are cached; If two Sprites have the same Image they share it; the Image hasn't been loaded twice or stored twice internally or anything. And if two Sprites start with the same Image you can make one have a different Image without affecting the other.

Using two Sprites with the same Image is very much like making two Material2D draw calls with the same Material2D. So just go ahead and create as many Sprites as you need.


Hope this helps clear a couple of things up :)

Edited by user Wednesday, May 29, 2013 3:31:54 AM(UTC)  | Reason: (Code tags were acting weird so had to change to italics!)

Offline internetfreak  
#3 Posted : Wednesday, May 29, 2013 9:32:23 AM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Hey PG, thanks for your answer
It seems that I expressed my problem a bit too confusing, sorry for that :D
Your answer is still helpful but it doesn't solve everything. Some of your details (like cached content) are already known by me, others are new.
Anyway, here's again my problem, this time a bit better ;)

I have a tilemap, let's say it's 20*15 tiles big. Each tile (I call it Cell because it fits the meaning better than "tile") has a position. I also have three layers for images to create a look like in other RPGs where there's the ground and on the ground there's a house or a tree or something else. Each cell has currently three integers (one for each layer) referring to the index of the image which is used on that layer (Images are stored differently in another class).
Earlier, I only needed to iterate through all cells (or at least all visible cells but I didn't have any camera class so it was basically all cells which were iterated and therefore I only used map dimensions which fitted the window) and get the texture index to resolve what image I need to draw. With that image, I created a Material2DColored, set the position to the position of that cell and changed the renderlayer of each material according to the layer it should be and drew it using Draw(). In the end, I got my desired result (I also have screenshots from that moment on my blog and also here in the forum but I think you know what I mean ;) )

Now with the Sprite class, I would basically do the same (and the only change would be that I have to add it to the renderer). The problem is that the Sprite class is now an entity and I have no control about rendering like before where I could be sure that the rendering list is empty and only all things are drawn which I added to the list.
With that problem in mind, I need to improve my code design. I could simply create 3 Sprites per cell to have all cells and their respective sprite but imo it's a bit too messy regarding memory etc even though the images are only loaded once.
I also would have to deactivate all sprites first and only activate all entites of the cells which are eligible for drawing (i.e those which are at least partly visible on the screen). It's a bit too much in my eyes and I have thoughts about performance if I have to compute visible cells each frame.
What I need is therefore something which could act like before so I can create only sprites for the visible cells and add them for rendering. That's the reason why I asked


I hope it's a bit more clear this time, I'm german and I have some problems to write in english (it works the most time but there are also times where I have trouble to write what I want to say)
I also don't like to get too technical about my projects first (all my projects are treated as secret until I decide it's time to release more informations) and that's why I often try to express my problems in a more general way, the disadvantage is often that my text is too confusing ;)
Still I hope you or someone else can help me, you can also contact me per PM or IM and I will probably share some more details with you (I don't want to do it in the publicity because it should be a surprise what I'm currently doing^^)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#4 Posted : Wednesday, May 29, 2013 1:23:18 PM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Originally Posted by: internetfreak Go to Quoted Post
I have a tilemap, let's say it's 20*15 tiles big. Each tile (I call it Cell because it fits the meaning better than "tile") has a position. I also have three layers for images to create a look like in other RPGs where there's the ground and on the ground there's a house or a tree or something else. Each cell has currently three integers (one for each layer) referring to the index of the image which is used on that layer (Images are stored differently in another class).
Earlier, I only needed to iterate through all cells (or at least all visible cells but I didn't have any camera class so it was basically all cells which were iterated and therefore I only used map dimensions which fitted the window) and get the texture index to resolve what image I need to draw. With that image, I created a Material2DColored, set the position to the position of that cell and changed the renderlayer of each material according to the layer it should be and drew it using Draw(). In the end, I got my desired result (I also have screenshots from that moment on my blog and also here in the forum but I think you know what I mean ;) )


Sure, if you want to do it this way, it's a little bit messier than before but you still can.

If you look at what Sprite.Render does, it's not very complicated and you can imitate it. Basically, instead of calling Material2D.Draw you call Drawing.DrawQuad. You will also need to copy the method logic to convert the quadratic coordinates to screen coordinates (actually vertices). (And then you've basically cloned Sprite.Render because that's all it does...)

Quote:
Now with the Sprite class, I would basically do the same (and the only change would be that I have to add it to the renderer). The problem is that the Sprite class is now an entity and I have no control about rendering like before where I could be sure that the rendering list is empty and only all things are drawn which I added to the list.


I get what you're saying. Sprites are persistent things: You tell it once to draw an image in a particular place, and it will do it forever more until you tell it to stop. Your way of working is to start from scratch every frame.

Well, the persistence can actually make things simpler. After all, how often does the contents of a Cell/Sprite change? Only if the state of the Cell changes (eg. a tree grows) or if the map scrolls. It could cut down your code from having to do processing every frame to becoming purely events-driven/reactive, which is obviously more efficient as a rule.

Also, by using Sprites, you can leverage the fact the engine will automatically batch draw calls by Image within a RenderLayer (it doesn't do this yet, but it soon will - perhaps even this week). By doing the draw calls yourself, if you had a naive loop that just looped across all cells swapping from one image to the next and back again, that would introduce a far bigger slowdown than anything we're discussing here. The code needs to loop by layer, then by image, then by cell (but you probably knew that :))

Quote:
With that problem in mind, I need to improve my code design. I could simply create 3 Sprites per cell to have all cells and their respective sprite but imo it's a bit too messy regarding memory etc even though the images are only loaded once.


The memory usage is really trivial either way. I mean, even when you did Material2D draw calls, it stored data internally roughly equivalent in size to 20*15*3=900 sprites anyway. Really, you should just code it in whatever way makes your code simplest and cleanest; 900 objects, even done highly inefficiently - which they won't be whatever way you choose - is not going to be any kind of bottleneck :)

Quote:
I also would have to deactivate all sprites first and only activate all entites of the cells which are eligible for drawing (i.e those which are at least partly visible on the screen). It's a bit too much in my eyes and I have thoughts about performance if I have to compute visible cells each frame.


Well, you shouldn't have to compute visible cells every frame. There are basically two ways you could pair up Sprites with Cells:
- A Sprite could represent a fixed area of the screen: When the map scrolls, you update the Image of each Sprite (you don't change anything else about a Sprite like DrawArea/IsActive).
- A Sprite could represent a fixed area of the world: When the map scrolls, you don't update any Sprite Image, you just change which Sprites are Active and Inactive. (If you have no Camera class you also need to update their DrawAreas, but with a suitable Camera class this part isn't necessary.)

(Personally I'd choose the first of those: Having Sprites for each subsection of the screen, and just update the Sprites' Images when the map scrolls or something changes. That way even if the logical map is, like, 1000x1000 tiles you still only have 900 Sprites.)

Basically, by coding reactively/events-based instead of starting from scratch every time, you should find your code becoming simpler and doing less each frame - because, after all, most frames nothing will change.


---

Feel free to throw me a PM or just continue to post in this thread if you wish to discuss things further; I'll be around on and off for the next couple of hours at least :)

- Phil

Edited by user Wednesday, May 29, 2013 1:31:29 PM(UTC)  | Reason: Not specified

Offline internetfreak  
#5 Posted : Wednesday, May 29, 2013 2:15:19 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
So you recommend me to have 3 Sprites per cell (one for each layer on that cell) and then only update the position and in rare occasions the image (the only thing where the image could change more often is in the editor, later in the game it's rarer to change an image but still possible)?

For the technical things, you are better experienced than I so your tipps are also appreciated. The only thing I want to do is have a good rendering which uses the systems of the engine.
Having the sprites is not really the problem, it's rather only drawing everything which is visible on the screen (does the engine already sort out not visible sprites?)

If you have something for me to make better use of the EntitySystem, then just tell me, I'm ready to learn :)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#6 Posted : Wednesday, May 29, 2013 2:37:56 PM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Originally Posted by: internetfreak Go to Quoted Post
So you recommend me to have 3 Sprites per cell (one for each layer on that cell) and then only update the position and in rare occasions the image (the only thing where the image could change more often is in the editor, later in the game it's rarer to change an image but still possible)?

For the technical things, you are better experienced than I so your tipps are also appreciated. The only thing I want to do is have a good rendering which uses the systems of the engine.
Having the sprites is not really the problem, it's rather only drawing everything which is visible on the screen (does the engine already sort out not visible sprites?)

If you have something for me to make better use of the EntitySystem, then just tell me, I'm ready to learn :)


Lets say that you have a 100x100 cell world - of which 20x15 can fit on screen at once. I would recommend creating 20x15x3 Sprites. Then when something happens (eg. the map scrolls) you loop through each Sprite and update the Image to what should be drawn at that spot on the screen.

Not all Cells will have Images for every layer, so obviously if there is an Image you set the corresponding Sprite to Active, and if there's no Image you set it to Inactive. (Equivalently you can set Sprite.Visibility to Show/Hide; That will affect just rendering with all the other handlers continuing to run, if there are any, and if that is important for you.)

You won't ever need to change the position (draw area) of a Sprite doing it this way - and you never need to worry about preventing drawing something that's off screen - because these Sprites by definition are rendering precisely only those Cells that are on screen.

That's my thoughts anyhow, but you should code it whatever way works out cleanest and easiest to understand :)


- Phil

Edited by user Wednesday, May 29, 2013 2:39:35 PM(UTC)  | Reason: Not specified

Offline internetfreak  
#7 Posted : Wednesday, May 29, 2013 2:53:29 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok, I see what you mean. Later, I will also need a camera so it should also be possible to render only partly visible sprites otherwise I will use tile based scrolling so I'm always scrolling at least one whole tile and then I would not need to cut off tiles which are only partly on the screen (it would also make the code cleaner)
The only thing I would have to do is a transition when scrolling so it changes the states smoothly and not abrupt, how would you do that?
I'm sorry for having you think for me project but it's still a bit new for me and I really want to do that. It's also the engine which is new although I'm working with Delta since December 2011 but ok that's based on the latest changes, once I mastered the EntitySystem a bit more I will be able to develop better games.

In any case I will try to implement your changes and then we see what happens.
Thank you for your help until now, this is something I really appreciate and that's the reason why I try to help Delta as much as I can with tutorials and other cool stuff because it's still give and take :)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#8 Posted : Wednesday, May 29, 2013 3:04:50 PM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Originally Posted by: internetfreak Go to Quoted Post
Ok, I see what you mean. Later, I will also need a camera so it should also be possible to render only partly visible sprites otherwise I will use tile based scrolling so I'm always scrolling at least one whole tile and then I would not need to cut off tiles which are only partly on the screen (it would also make the code cleaner)
The only thing I would have to do is a transition when scrolling so it changes the states smoothly and not abrupt, how would you do that?


If you want smooth scrolling I suggest you have one more sprite in each direction than you need: ie. Instead of a grid of 20x15x3 Sprites you have 22x17x3

Then you do have to adjust the DrawAreas of each of the Sprites so that the ones on the edges lie partly on and partly off - but you always adjust the DrawArea by less than the size of one tile. You will then want to also draw a border around the tilemap so the player can still only see the subsection 20x15 in size.

I think maybe I'll knock up a quick sample code, and, if it's small enough, I'll post it here :)
thanks 1 user thanked elasto for this useful post.
internetfreak on 5/29/2013(UTC)
Offline internetfreak  
#9 Posted : Wednesday, May 29, 2013 4:11:42 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok, a bit sample code is never wrong
For now I will first try to implement a static map, that means I will limit it to 20*15 without scrolling or something else so I'm able to implement the render mechanism of the map (everything else is already nicely abstracted by a base class as I probably want to implement more than one map type but I'm not sure how it would be)
Later I will look how I can use the Camera2DControlledScreenSpace as it looks like a useable solution so I don't have to implement an own camera class (saves time)

Again, if you have another helpful things to help a young padawan like me, then just go on and share whatever you want to share, I'm glad for each help I will get and I will definitely give something back sooner or later
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#10 Posted : Wednesday, May 29, 2013 5:12:11 PM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Ok, the following should work, assuming no engine change which causes this not to build for you. It doesn't use anything other than Sprites and Rects (for the frame around the tilemap).

Edit: Actually, there is one thing that won't compile for you: Change the input.Add lines from:
input.Add(Key.CursorLeft, key =>
to
input.Add(Key.CursorLeft, () =>

---

This demos a smoothly scrolling tile map. I am making my 'cells' different colors rather than different images, but obviously it's the same principle.

(This post doesn't display properly for me - it cuts off everything before the 'VisualTest' line - I guess the square brackets in the code are breaking the BB code? So I'm including the code as a 'quote' too - copy and paste that into a new class to run this.)

Code:

using System;
using DeltaEngine.Content;
using DeltaEngine.Core;
using DeltaEngine.Datatypes;
using DeltaEngine.Graphics;
using DeltaEngine.Input;
using DeltaEngine.Platforms;
using DeltaEngine.Platforms.All;
using DeltaEngine.Rendering.Shapes;
using DeltaEngine.Rendering.Sprites;

namespace DeltaEngine.Scenes.Tests
{
	/// <summary>
	/// A quick demo/tutorial of a smoothly scrolling tilemap
	/// </summary>
	public class TileMapTutorial : TestWithAllFrameworks
	{
		[VisualTest]
		public void RenderSmoothlyScrollingTileMap(Type resolver)
		{
			Start(resolver, (ContentLoader content, InputCommands input) =>
			{
				logo = content.Load<Image>("DeltaEngineLogo");
				CreateWorld();
				CreateMap();
				CreateBorder();
				RespondToInput(input);
			}, (Window window) =>
			{
				window.Title = "RenderSmoothlyScrollingTileMap: use cursor keys to scroll around";
				ScrollTopLeft();
			});
		}

		private Image logo;

		private void CreateWorld()
		{
			for (int x = 0; x < WorldWidth; x++)
				for (int y = 0; y < WorldHeight; y++)
					world[x, y] = new Color(Rainbow(x, WorldWidth), Rainbow(y, WorldHeight),
						Rainbow(x + y, WorldWidth));
		}

		private readonly Color[,] world = new Color[WorldWidth,WorldHeight];
		private const int WorldWidth = 30;
		private const int WorldHeight = 30;

		private static float Rainbow(int value, int max)
		{
			return ((2.0f * value) % max) / max;
		}

		private void CreateMap()
		{
			for (int x = 0; x < MapWidth; x++)
				for (int y = 0; y < MapHeight; y++)
					map[x, y] = new Sprite(logo,
						new Rectangle(MapLeft + x * TileWidth, MapTop + y * TileHeight, TileWidth, TileHeight));

			UpdateMapSprites();
		}

		private readonly Sprite[,] map = new Sprite[MapWidth,MapHeight];
		private const int MapWidth = 10;
		private const int MapHeight = 10;
		private const float MapLeft = 0.1f;
		private const float MapTop = 0.3f;
		private const float TileWidth = 0.04f;
		private const float TileHeight = 0.04f;

		private void UpdateMapSprites()
		{
			var offset = new Point(renderingTopLeft.X % 1.0f, renderingTopLeft.Y % 1.0f);
			for (int x = 0; x < MapWidth; x++)
				for (int y = 0; y < MapHeight; y++)
					UpdateMapSprite(x, y, offset);
		}

		private Point renderingTopLeft;

		private void UpdateMapSprite(int x, int y, Point offset)
		{
			map[x, y].Color = world[(int)renderingTopLeft.X + x, (int)renderingTopLeft.Y + y];
			map[x, y].DrawArea = new Rectangle(MapLeft + (x - offset.X) * TileWidth,
				MapTop + (y - offset.Y) * TileHeight, TileWidth, TileHeight);
		}

		private static void CreateBorder()
		{
			new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, BorderWidth, TileHeight),
				Color.Gray);
			new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, TileWidth, BorderHeight),
				Color.Gray);
			new Rect(
				new Rectangle(MapLeft - TileWidth, MapTop + (MapHeight - 1) * TileHeight, BorderWidth,
					TileHeight), Color.Gray);
			new Rect(
				new Rectangle(MapLeft + (MapWidth - 1) * TileWidth, MapTop - TileHeight, TileWidth,
					BorderHeight), Color.Gray);
		}

		private const float BorderWidth = (MapWidth + 1) * TileWidth;
		private const float BorderHeight = (MapHeight + 1) * TileHeight;

		private void RespondToInput(InputCommands input)
		{
			input.Add(Key.CursorLeft, key =>
			{
				if (desiredTopLeft.X > 0)
					desiredTopLeft.X--;
			});

			input.Add(Key.CursorRight, key =>
			{
				if (desiredTopLeft.X < WorldWidth - MapWidth)
					desiredTopLeft.X++;
			});

			input.Add(Key.CursorUp, key =>
			{
				if (desiredTopLeft.Y > 0)
					desiredTopLeft.Y--;
			});

			input.Add(Key.CursorDown, key =>
			{
				if (desiredTopLeft.Y < WorldHeight - MapHeight)
					desiredTopLeft.Y++;
			});
		}

		private Point desiredTopLeft;

		private void ScrollTopLeft()
		{
			if (renderingTopLeft == desiredTopLeft)
				return;

			renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X,
				Time.Current.Delta);
			renderingTopLeft.Y = MathExtensions.Lerp(renderingTopLeft.Y, desiredTopLeft.Y,
				Time.Current.Delta);
			UpdateMapSprites();
		}
	}
}


Quote:
using System;
using DeltaEngine.Content;
using DeltaEngine.Core;
using DeltaEngine.Datatypes;
using DeltaEngine.Graphics;
using DeltaEngine.Input;
using DeltaEngine.Platforms;
using DeltaEngine.Platforms.All;
using DeltaEngine.Rendering.Shapes;
using DeltaEngine.Rendering.Sprites;

namespace DeltaEngine.Scenes.Tests
{
/// <summary>
/// A quick demo/tutorial of a smoothly scrolling tilemap
/// </summary>
public class TileMapTutorial : TestWithAllFrameworks
{
[VisualTest]
public void RenderSmoothlyScrollingTileMap(Type resolver)
{
Start(resolver, (ContentLoader content, InputCommands input) =>
{
logo = content.Load<Image>("DeltaEngineLogo");
CreateWorld();
CreateMap();
CreateBorder();
RespondToInput(input);
}, (Window window) =>
{
window.Title = "RenderSmoothlyScrollingTileMap: use cursor keys to scroll around";
ScrollTopLeft();
});
}

private Image logo;

private void CreateWorld()
{
for (int x = 0; x < WorldWidth; x++)
for (int y = 0; y < WorldHeight; y++)
world[x, y] = new Color(Rainbow(x, WorldWidth), Rainbow(y, WorldHeight),
Rainbow(x + y, WorldWidth));
}

private readonly Color[,] world = new Color[WorldWidth,WorldHeight];
private const int WorldWidth = 30;
private const int WorldHeight = 30;

private static float Rainbow(int value, int max)
{
return ((2.0f * value) % max) / max;
}

private void CreateMap()
{
for (int x = 0; x < MapWidth; x++)
for (int y = 0; y < MapHeight; y++)
map[x, y] = new Sprite(logo,
new Rectangle(MapLeft + x * TileWidth, MapTop + y * TileHeight, TileWidth, TileHeight));

UpdateMapSprites();
}

private readonly Sprite[,] map = new Sprite[MapWidth,MapHeight];
private const int MapWidth = 10;
private const int MapHeight = 10;
private const float MapLeft = 0.1f;
private const float MapTop = 0.3f;
private const float TileWidth = 0.04f;
private const float TileHeight = 0.04f;

private void UpdateMapSprites()
{
var offset = new Point(renderingTopLeft.X % 1.0f, renderingTopLeft.Y % 1.0f);
for (int x = 0; x < MapWidth; x++)
for (int y = 0; y < MapHeight; y++)
UpdateMapSprite(x, y, offset);
}

private Point renderingTopLeft;

private void UpdateMapSprite(int x, int y, Point offset)
{
map[x, y].Color = world[(int)renderingTopLeft.X + x, (int)renderingTopLeft.Y + y];
map[x, y].DrawArea = new Rectangle(MapLeft + (x - offset.X) * TileWidth,
MapTop + (y - offset.Y) * TileHeight, TileWidth, TileHeight);
}

private static void CreateBorder()
{
new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, BorderWidth, TileHeight),
Color.Gray);
new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, TileWidth, BorderHeight),
Color.Gray);
new Rect(
new Rectangle(MapLeft - TileWidth, MapTop + (MapHeight - 1) * TileHeight, BorderWidth,
TileHeight), Color.Gray);
new Rect(
new Rectangle(MapLeft + (MapWidth - 1) * TileWidth, MapTop - TileHeight, TileWidth,
BorderHeight), Color.Gray);
}

private const float BorderWidth = (MapWidth + 1) * TileWidth;
private const float BorderHeight = (MapHeight + 1) * TileHeight;

private void RespondToInput(InputCommands input)
{
input.Add(Key.CursorLeft, key =>
{
if (desiredTopLeft.X > 0)
desiredTopLeft.X--;
});

input.Add(Key.CursorRight, key =>
{
if (desiredTopLeft.X < WorldWidth - MapWidth)
desiredTopLeft.X++;
});

input.Add(Key.CursorUp, key =>
{
if (desiredTopLeft.Y > 0)
desiredTopLeft.Y--;
});

input.Add(Key.CursorDown, key =>
{
if (desiredTopLeft.Y < WorldHeight - MapHeight)
desiredTopLeft.Y++;
});
}

private Point desiredTopLeft;

private void ScrollTopLeft()
{
if (renderingTopLeft == desiredTopLeft)
return;

renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X,
Time.Current.Delta);
renderingTopLeft.Y = MathExtensions.Lerp(renderingTopLeft.Y, desiredTopLeft.Y,
Time.Current.Delta);
UpdateMapSprites();
}
}
}

Edited by user Wednesday, May 29, 2013 5:28:41 PM(UTC)  | Reason: Not specified

thanks 1 user thanked elasto for this useful post.
internetfreak on 5/29/2013(UTC)
Offline internetfreak  
#11 Posted : Wednesday, May 29, 2013 5:38:30 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Thank you very much, I will try it out when I'm at home. If something does not work or I have more questions, I will ask here :D
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline internetfreak  
#12 Posted : Wednesday, May 29, 2013 10:59:31 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok, I tried the sample and it's basically good, I only have to figure out how I can integrate it in my project (regarding class design etc)
One disadvantage is that it scrolls way too fast if I modify it that it uses State.Pressing to allow scrolling while holding the key (like in any game)
I also have to make it work that it stop scrolling immediately when the hero stops or at least scrolls a bit more abruptly but then stops asap if the player releases the key (like in other rpgs)
maybe you can also help me on that but for now I will first implement a static map as I said ealier. How can I use that sample with the Camera2DControlledScreenSpace as camera or do I not need any special camera class (instead I only use a MapRenderer for the sake of memory, it does not make sense to have a grid of sprites for rendering per map, I rather prefer to declare it once and use it with any map because I can only draw one map at once)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#13 Posted : Thursday, May 30, 2013 2:34:41 AM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Originally Posted by: internetfreak Go to Quoted Post
Ok, I tried the sample and it's basically good, I only have to figure out how I can integrate it in my project (regarding class design etc)
One disadvantage is that it scrolls way too fast if I modify it that it uses State.Pressing to allow scrolling while holding the key (like in any game)
I also have to make it work that it stop scrolling immediately when the hero stops or at least scrolls a bit more abruptly but then stops asap if the player releases the key (like in other rpgs)


Well, you've got a few options here.

If you want to retain keyboard control, instead of doing "desiredTopLeft.X++" on a keypress, you could do "desiredTopLeft.X += Time.Current.Delta" while a key is down.

If you want mouse/touch control, you could have "renderingTopLeft.X += (amount mouse/touch was dragged this frame)", panning like a tablet game would (foregoing any rubber-banding at all)

Or, you could disallow direct control altogether: You could have the keys/mouse/touch control the hero and then have the map auto-center on him. You could either lock him absolutely to the center or allow a bit of a rubber-band effect by having a rendering-desired pairing as I did here (making the rubber band effect stronger the further the hero gets from the center, so they can never reach the edge).

Lots of options possible depending on taste :)

Quote:
How can I use that sample with the Camera2DControlledScreenSpace as camera or do I not need any special camera class (instead I only use a MapRenderer for the sake of memory, it does not make sense to have a grid of sprites for rendering per map, I rather prefer to declare it once and use it with any map because I can only draw one map at once)

Well, with this setup you don't actually need a camera, because it's always rendering only that which is on screen. A camera is for if we're not altering the Sprite Images and positions ourselves, but we are.

To do zooming you'd have to make the Sprites bigger and smaller, which also means creating/disposing them on zooming out/in, but that's not too much harder than we've already done.

Edited by user Thursday, May 30, 2013 3:25:42 AM(UTC)  | Reason: Added more options for input controls

Offline elasto  
#14 Posted : Thursday, May 30, 2013 6:04:59 AM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Ok so I updated the sample so holding a cursor key scrolls and also clicking and dragging the mouse pans the tilemap.

I've created this as a Visual Test in SpriteTests, but it's getting complicated enough that it'll probably end up as a Sample before too long.

(again, quoting it as well as including in code tags as it's not rendering properly for me...)

Code:
using System;
using DeltaEngine.Content;
using DeltaEngine.Core;
using DeltaEngine.Datatypes;
using DeltaEngine.Graphics;
using DeltaEngine.Input;
using DeltaEngine.Platforms.All;
using DeltaEngine.Platforms.Tests;
using DeltaEngine.Rendering.Fonts;
using DeltaEngine.Rendering.Shapes;
using DeltaEngine.Rendering.Sprites;
using NUnit.Framework;

namespace DeltaEngine.Rendering.Tests.Sprites
{
	public class SpriteTests : TestWithAllFrameworks
	{
		[VisualTest]
		public void RenderSmoothlyScrollingTileMap(Type resolver)
		{
			Start(resolver, (ContentLoader content, InputCommands input) =>
			{
				new FontText(new Font(content, "Verdana12"),
					"Hold down cursor keys or drag mouse to scroll", new Point(0.3f, 0.75f));
				logo = content.Load<Image>("DeltaEngineLogo");
				CreateWorld();
				CreateMap();
				CreateBorder();
				RespondToInput(input);
			}, ScrollMap);
		}

		private Image logo;

		private void CreateWorld()
		{
			for (int x = 0; x < WorldWidth; x++)
				for (int y = 0; y < WorldHeight; y++)
					world[x, y] = new Color(Rainbow(x, WorldWidth), Rainbow(y, WorldHeight),
						Rainbow(x + y, WorldWidth));
		}

		private readonly Color[,] world = new Color[WorldWidth,WorldHeight];
		private const int WorldWidth = 30;
		private const int WorldHeight = 30;

		private static float Rainbow(int value, int max)
		{
			return ((2.0f * value) % max) / max;
		}

		private void CreateMap()
		{
			for (int x = 0; x < MapWidth; x++)
				for (int y = 0; y < MapHeight; y++)
					map[x, y] = new Sprite(logo);

			UpdateMapSprites();
		}

		private readonly Sprite[,] map = new Sprite[MapWidth,MapHeight];
		private const int MapWidth = 10;
		private const int MapHeight = 10;
		private const float MapLeft = 0.1f;
		private const float MapTop = 0.3f;
		private const float TileWidth = 0.04f;
		private const float TileHeight = 0.04f;

		private void UpdateMapSprites()
		{
			var offset = new Point(renderingTopLeft.X % 1.0f, renderingTopLeft.Y % 1.0f);
			for (int x = 0; x < MapWidth; x++)
				for (int y = 0; y < MapHeight; y++)
					UpdateMapSprite(x, y, offset);
		}

		private Point renderingTopLeft;

		private void UpdateMapSprite(int x, int y, Point offset)
		{
			map[x, y].Color = world[(int)renderingTopLeft.X + x, (int)renderingTopLeft.Y + y];
			map[x, y].DrawArea = new Rectangle(MapLeft + (x - offset.X) * TileWidth,
				MapTop + (y - offset.Y) * TileHeight, TileWidth, TileHeight);
		}

		private static void CreateBorder()
		{
			new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, BorderWidth, TileHeight),
				Color.Gray);
			new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, TileWidth, BorderHeight),
				Color.Gray);
			new Rect(
				new Rectangle(MapLeft - TileWidth, MapTop + (MapHeight - 1) * TileHeight, BorderWidth,
					TileHeight), Color.Gray);
			new Rect(
				new Rectangle(MapLeft + (MapWidth - 1) * TileWidth, MapTop - TileHeight, TileWidth,
					BorderHeight), Color.Gray);
		}

		private const float BorderWidth = (MapWidth + 1) * TileWidth;
		private const float BorderHeight = (MapHeight + 1) * TileHeight;

		private void RespondToInput(InputCommands input)
		{
			input.Add(MouseButton.Left, State.Pressing, BeginMouseDrag);
			input.Add(MouseButton.Left, State.Pressed, ContinueMouseDrag);
			input.Add(Key.CursorLeft, State.Pressed, key => CursorLeftPressed());
			input.Add(Key.CursorRight, State.Pressed, key => CursorRightPressed());
			input.Add(Key.CursorUp, State.Pressed, key => CursorUpPressed());
			input.Add(Key.CursorDown, State.Pressed, key => CursorDownPressed());
		}

		private void BeginMouseDrag(Mouse mouse)
		{
			lastMousePosition = mouse.Position;
		}

		private Point lastMousePosition;

		private void ContinueMouseDrag(Mouse mouse)
		{
			desiredTopLeft.X += (lastMousePosition.X - mouse.Position.X) / TileWidth;
			desiredTopLeft.Y += (lastMousePosition.Y - mouse.Position.Y) / TileHeight;
			lastMousePosition = mouse.Position;
			RestrictScrollingToWithinWorld();
		}

		private Point desiredTopLeft;

		private void RestrictScrollingToWithinWorld()
		{
			if (desiredTopLeft.X < 0)
				desiredTopLeft.X = 0;

			if (desiredTopLeft.X > WorldWidth - MapWidth)
				desiredTopLeft.X = WorldWidth - MapWidth;

			if (desiredTopLeft.Y < 0)
				desiredTopLeft.Y = 0;

			if (desiredTopLeft.Y > WorldHeight - MapHeight)
				desiredTopLeft.Y = WorldHeight - MapHeight;
		}

		private void CursorLeftPressed()
		{
			desiredTopLeft.X -= Time.Current.Delta * ScrollSpeed;
			RestrictScrollingToWithinWorld();
		}

		private const float ScrollSpeed = 4.0f;

		private void CursorRightPressed()
		{
			desiredTopLeft.X += Time.Current.Delta * ScrollSpeed;
			RestrictScrollingToWithinWorld();
		}

		private void CursorUpPressed()
		{
			desiredTopLeft.Y -= Time.Current.Delta * ScrollSpeed;
			RestrictScrollingToWithinWorld();
		}

		private void CursorDownPressed()
		{
			desiredTopLeft.Y += Time.Current.Delta * ScrollSpeed;
			RestrictScrollingToWithinWorld();
		}

		private void ScrollMap()
		{
			if (renderingTopLeft == desiredTopLeft)
				return;

			renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X,
				Time.Current.Delta);
			renderingTopLeft.Y = MathExtensions.Lerp(renderingTopLeft.Y, desiredTopLeft.Y,
				Time.Current.Delta);
			UpdateMapSprites();
		}
	}
}


Quote:
using System;
using DeltaEngine.Content;
using DeltaEngine.Core;
using DeltaEngine.Datatypes;
using DeltaEngine.Graphics;
using DeltaEngine.Input;
using DeltaEngine.Platforms.All;
using DeltaEngine.Platforms.Tests;
using DeltaEngine.Rendering.Fonts;
using DeltaEngine.Rendering.Shapes;
using DeltaEngine.Rendering.Sprites;
using NUnit.Framework;

namespace DeltaEngine.Rendering.Tests.Sprites
{
public class SpriteTests : TestWithAllFrameworks
{
[VisualTest]
public void RenderSmoothlyScrollingTileMap(Type resolver)
{
Start(resolver, (ContentLoader content, InputCommands input) =>
{
new FontText(new Font(content, "Verdana12"),
"Hold down cursor keys or drag mouse to scroll", new Point(0.3f, 0.75f));
logo = content.Load<Image>("DeltaEngineLogo");
CreateWorld();
CreateMap();
CreateBorder();
RespondToInput(input);
}, ScrollMap);
}

private Image logo;

private void CreateWorld()
{
for (int x = 0; x < WorldWidth; x++)
for (int y = 0; y < WorldHeight; y++)
world[x, y] = new Color(Rainbow(x, WorldWidth), Rainbow(y, WorldHeight),
Rainbow(x + y, WorldWidth));
}

private readonly Color[,] world = new Color[WorldWidth,WorldHeight];
private const int WorldWidth = 30;
private const int WorldHeight = 30;

private static float Rainbow(int value, int max)
{
return ((2.0f * value) % max) / max;
}

private void CreateMap()
{
for (int x = 0; x < MapWidth; x++)
for (int y = 0; y < MapHeight; y++)
map[x, y] = new Sprite(logo);

UpdateMapSprites();
}

private readonly Sprite[,] map = new Sprite[MapWidth,MapHeight];
private const int MapWidth = 10;
private const int MapHeight = 10;
private const float MapLeft = 0.1f;
private const float MapTop = 0.3f;
private const float TileWidth = 0.04f;
private const float TileHeight = 0.04f;

private void UpdateMapSprites()
{
var offset = new Point(renderingTopLeft.X % 1.0f, renderingTopLeft.Y % 1.0f);
for (int x = 0; x < MapWidth; x++)
for (int y = 0; y < MapHeight; y++)
UpdateMapSprite(x, y, offset);
}

private Point renderingTopLeft;

private void UpdateMapSprite(int x, int y, Point offset)
{
map[x, y].Color = world[(int)renderingTopLeft.X + x, (int)renderingTopLeft.Y + y];
map[x, y].DrawArea = new Rectangle(MapLeft + (x - offset.X) * TileWidth,
MapTop + (y - offset.Y) * TileHeight, TileWidth, TileHeight);
}

private static void CreateBorder()
{
new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, BorderWidth, TileHeight),
Color.Gray);
new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, TileWidth, BorderHeight),
Color.Gray);
new Rect(
new Rectangle(MapLeft - TileWidth, MapTop + (MapHeight - 1) * TileHeight, BorderWidth,
TileHeight), Color.Gray);
new Rect(
new Rectangle(MapLeft + (MapWidth - 1) * TileWidth, MapTop - TileHeight, TileWidth,
BorderHeight), Color.Gray);
}

private const float BorderWidth = (MapWidth + 1) * TileWidth;
private const float BorderHeight = (MapHeight + 1) * TileHeight;

private void RespondToInput(InputCommands input)
{
input.Add(MouseButton.Left, State.Pressing, BeginMouseDrag);
input.Add(MouseButton.Left, State.Pressed, ContinueMouseDrag);
input.Add(Key.CursorLeft, State.Pressed, key => CursorLeftPressed());
input.Add(Key.CursorRight, State.Pressed, key => CursorRightPressed());
input.Add(Key.CursorUp, State.Pressed, key => CursorUpPressed());
input.Add(Key.CursorDown, State.Pressed, key => CursorDownPressed());
}

private void BeginMouseDrag(Mouse mouse)
{
lastMousePosition = mouse.Position;
}

private Point lastMousePosition;

private void ContinueMouseDrag(Mouse mouse)
{
desiredTopLeft.X += (lastMousePosition.X - mouse.Position.X) / TileWidth;
desiredTopLeft.Y += (lastMousePosition.Y - mouse.Position.Y) / TileHeight;
lastMousePosition = mouse.Position;
RestrictScrollingToWithinWorld();
}

private Point desiredTopLeft;

private void RestrictScrollingToWithinWorld()
{
if (desiredTopLeft.X < 0)
desiredTopLeft.X = 0;

if (desiredTopLeft.X > WorldWidth - MapWidth)
desiredTopLeft.X = WorldWidth - MapWidth;

if (desiredTopLeft.Y < 0)
desiredTopLeft.Y = 0;

if (desiredTopLeft.Y > WorldHeight - MapHeight)
desiredTopLeft.Y = WorldHeight - MapHeight;
}

private void CursorLeftPressed()
{
desiredTopLeft.X -= Time.Current.Delta * ScrollSpeed;
RestrictScrollingToWithinWorld();
}

private const float ScrollSpeed = 4.0f;

private void CursorRightPressed()
{
desiredTopLeft.X += Time.Current.Delta * ScrollSpeed;
RestrictScrollingToWithinWorld();
}

private void CursorUpPressed()
{
desiredTopLeft.Y -= Time.Current.Delta * ScrollSpeed;
RestrictScrollingToWithinWorld();
}

private void CursorDownPressed()
{
desiredTopLeft.Y += Time.Current.Delta * ScrollSpeed;
RestrictScrollingToWithinWorld();
}

private void ScrollMap()
{
if (renderingTopLeft == desiredTopLeft)
return;

renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X,
Time.Current.Delta);
renderingTopLeft.Y = MathExtensions.Lerp(renderingTopLeft.Y, desiredTopLeft.Y,
Time.Current.Delta);
UpdateMapSprites();
}
}
}

Edited by user Thursday, May 30, 2013 6:05:54 AM(UTC)  | Reason: Not specified

Offline internetfreak  
#15 Posted : Thursday, May 30, 2013 11:39:38 AM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Very much thanks, I will try it out later.
I hope my questions are not creating the "I'm a noob and cannot do anything on my own"-mood. In fact, the Tilemap rendering and the correct camera are two problems for me now so I wanted to solve them and that's why I asked :)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#16 Posted : Thursday, May 30, 2013 11:42:26 AM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
Nah. A tilemap sample has been on our todo list for a while now anyhow :)
Offline internetfreak  
#17 Posted : Thursday, May 30, 2013 2:27:32 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok then I think it's good that I asked so you had the chance to get things removed from your TODO list :D
I will continue asking, maybe there's more planned stuff which I could help to bring forward with questions^^
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline internetfreak  
#18 Posted : Thursday, May 30, 2013 5:23:20 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
I modified the sample a little bit, making the map bigger and stopping scrolling as soon as the key is released (like when you stop the hero, then scrolling also stops)
It may not be the best way how I did it but I think it's no problem. Feel free to include that also in the Engine samples as it's based on your code, I only modified it a bit so I get the result which I want to have in my project.
The only thing which has to be changed is when you move diagonally and release one key, the map stops abruptly for a short period before it starts moving again. Here it would be better to only stop the directional moving of the key which was released (if I release Up, then only stop Y-axis and not both axis)

Code:
using System;
using DeltaEngine.Content;
using DeltaEngine.Core;
using DeltaEngine.Datatypes;
using DeltaEngine.Graphics;
using DeltaEngine.Input;
using DeltaEngine.Platforms.All;
using DeltaEngine.Platforms.Tests;
using DeltaEngine.Rendering.Fonts;
using DeltaEngine.Rendering.Shapes;
using DeltaEngine.Rendering.Sprites;
using NUnit.Framework;

namespace DeltaEngine.Rendering.Tests.Sprites
{
    public class TileMapTutorial : TestWithAllFrameworks
    {
        [VisualTest]
        public void RenderSmoothlyScrollingTileMap(Type resolver)
        {
            Start(resolver, (ContentLoader content, InputCommands input) =>
            {
                //new FontText(new Font(content, "Verdana12"),
                //"Hold down cursor keys or drag mouse to scroll", new Point(0.3f, 0.75f));
                logo = content.Load<Image>("DeltaEngineLogo");
                CreateWorld();
                CreateMap();
                CreateBorder();
                RespondToInput(input);
            }, ScrollMap);
        }

        private Image logo;

        private void CreateWorld()
        {
            for (int x = 0; x < WorldWidth; x++)
                for (int y = 0; y < WorldHeight; y++)
                    world[x, y] = new Color(Rainbow(x, WorldWidth), Rainbow(y, WorldHeight),
                    Rainbow(x + y, WorldWidth));
        }

        private readonly Color[,] world = new Color[WorldWidth, WorldHeight];
        private const int WorldWidth = 120;
        private const int WorldHeight = 120;

        private static float Rainbow(int value, int max)
        {
            return ((2.0f * value) % max) / max;
        }

        private void CreateMap()
        {
            for (int x = 0; x < MapWidth; x++)
                for (int y = 0; y < MapHeight; y++)
                    map[x, y] = new Sprite(logo,
                        new Rectangle(MapLeft + x * TileWidth, MapTop + y * TileHeight, TileWidth, TileHeight));

            UpdateMapSprites();
        }

        private readonly Sprite[,] map = new Sprite[MapWidth, MapHeight];
        private const int MapWidth = 10;
        private const int MapHeight = 10;
        private const float MapLeft = 0.1f;
        private const float MapTop = 0.3f;
        private const float TileWidth = 0.04f;
        private const float TileHeight = 0.04f;

        private void UpdateMapSprites()
        {
            var offset = new Point(renderingTopLeft.X % 1.0f, renderingTopLeft.Y % 1.0f);
            for (int x = 0; x < MapWidth; x++)
                for (int y = 0; y < MapHeight; y++)
                    UpdateMapSprite(x, y, offset);
        }

        private Point renderingTopLeft;

        private void UpdateMapSprite(int x, int y, Point offset)
        {
            map[x, y].Color = world[(int)renderingTopLeft.X + x, (int)renderingTopLeft.Y + y];
            map[x, y].DrawArea = new Rectangle(MapLeft + (x - offset.X) * TileWidth,
            MapTop + (y - offset.Y) * TileHeight, TileWidth, TileHeight);
        }

        private static void CreateBorder()
        {
            new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, BorderWidth, TileHeight),
            Color.Gray);
            new Rect(new Rectangle(MapLeft - TileWidth, MapTop - TileHeight, TileWidth, BorderHeight),
            Color.Gray);
            new Rect(
            new Rectangle(MapLeft - TileWidth, MapTop + (MapHeight - 1) * TileHeight, BorderWidth,
            TileHeight), Color.Gray);
            new Rect(
            new Rectangle(MapLeft + (MapWidth - 1) * TileWidth, MapTop - TileHeight, TileWidth,
            BorderHeight), Color.Gray);
        }

        private const float BorderWidth = (MapWidth + 1) * TileWidth;
        private const float BorderHeight = (MapHeight + 1) * TileHeight;

        private void RespondToInput(InputCommands input)
        {
            input.Add(MouseButton.Left, State.Pressing, BeginMouseDrag);
            input.Add(MouseButton.Left, State.Pressed, ContinueMouseDrag);
            input.Add(MouseButton.Left, State.Releasing, StopScrolling);
            input.Add(Key.CursorLeft, State.Pressed, () => CursorLeftPressed());
            input.Add(Key.CursorRight, State.Pressed, () => CursorRightPressed());
            input.Add(Key.CursorUp, State.Pressed, () => CursorUpPressed());
            input.Add(Key.CursorDown, State.Pressed, () => CursorDownPressed());

            input.Add(Key.CursorLeft, State.Releasing, () => StopScrolling(null));
            input.Add(Key.CursorRight, State.Releasing, () => StopScrolling(null));
            input.Add(Key.CursorUp, State.Releasing, () => StopScrolling(null));
            input.Add(Key.CursorDown, State.Releasing, () => StopScrolling(null));
        }

        private void BeginMouseDrag(Mouse mouse)
        {
            lastMousePosition = mouse.Position;

            if (!ScrollingEnabled)
                ScrollingEnabled = true;
        }

        private Point lastMousePosition;

        private void ContinueMouseDrag(Mouse mouse)
        {
            desiredTopLeft.X += (lastMousePosition.X - mouse.Position.X) / TileWidth;
            desiredTopLeft.Y += (lastMousePosition.Y - mouse.Position.Y) / TileHeight;
            lastMousePosition = mouse.Position;
            RestrictScrollingToWithinWorld();
        }

        private void StopScrolling(Mouse mouse)
        {
            desiredTopLeft = renderingTopLeft;
            ScrollingEnabled = false;
        }

        private Point desiredTopLeft;

        private void RestrictScrollingToWithinWorld()
        {
            if (desiredTopLeft.X < 0)
                desiredTopLeft.X = 0;

            if (desiredTopLeft.X > WorldWidth - MapWidth)
                desiredTopLeft.X = WorldWidth - MapWidth;

            if (desiredTopLeft.Y < 0)
                desiredTopLeft.Y = 0;

            if (desiredTopLeft.Y > WorldHeight - MapHeight)
                desiredTopLeft.Y = WorldHeight - MapHeight;
        }

        private void CursorLeftPressed()
        {
            if(!ScrollingEnabled)
                ScrollingEnabled = true;

            desiredTopLeft.X -= Time.Current.Delta * ScrollSpeed;
            RestrictScrollingToWithinWorld();
        }

        private const float ScrollSpeed = 12.0f;

        private void CursorRightPressed()
        {
            if (!ScrollingEnabled)
                ScrollingEnabled = true;

            desiredTopLeft.X += Time.Current.Delta * ScrollSpeed;
            RestrictScrollingToWithinWorld();
        }

        private void CursorUpPressed()
        {
            if (!ScrollingEnabled)
                ScrollingEnabled = true;

            desiredTopLeft.Y -= Time.Current.Delta * ScrollSpeed;
            RestrictScrollingToWithinWorld();
        }

        private void CursorDownPressed()
        {
            if (!ScrollingEnabled)
                ScrollingEnabled = true;

            desiredTopLeft.Y += Time.Current.Delta * ScrollSpeed;
            RestrictScrollingToWithinWorld();
        }

        private void ScrollMap()
        {
            if (!ScrollingEnabled)
                return;

            if (renderingTopLeft == desiredTopLeft)
                return;

            renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X,
            Time.Current.Delta);
            renderingTopLeft.Y = MathExtensions.Lerp(renderingTopLeft.Y, desiredTopLeft.Y,
            Time.Current.Delta);
            UpdateMapSprites();
        }

        public bool ScrollingEnabled = true;
    }
}

Edited by user Thursday, May 30, 2013 5:25:00 PM(UTC)  | Reason: Not specified

Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline Flavio Damasco  
#19 Posted : Friday, May 31, 2013 11:27:33 AM(UTC)
Flavio Damasco

Joined: 5/15/2013(UTC)
Posts: 45
Location: Hannover

Was thanked: 2 time(s) in 2 post(s)
Great job! I will inform the programmers about this!
Thanks!
UserPostedImage
Offline elasto  
#20 Posted : Friday, May 31, 2013 11:38:26 AM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
I haven't tried out your changes yet but thanks for the input :)

I think personally I wouldn't go for an abrupt halt on releasing the key - I'd prefer just to make the map follow the movement more 'stiffly' if that were required; ie. instead of taking a couple of seconds to come to a halt it closes in over a fraction of a second.

To do that you'd just change this line of code:
renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X, Time.Current.Delta);
to this:
renderingTopLeft.X = MathExtensions.Lerp(renderingTopLeft.X, desiredTopLeft.X, 2 * Time.Current.Delta);

(and the same for renderingTopLeft.Y, obviously)

The larger you make that multiplier, the more closely and 'stiffly' rendering will follow desired, and the smaller you make it the slower it will converge.

But it's all a matter of personal preference obviously :)
Offline internetfreak  
#21 Posted : Friday, May 31, 2013 2:29:17 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok, I will try it out, we will see what's fitting better :)
Yesterday, I managed to come a little bit more forward, today I will probably start the rendering and then I will share some screens with you.

@PG
Do you maybe have a tipp how I can fill in my 3*x*y Sprites efficiently and only use one Rectangle as position (as all three images per cell have the same pos)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline elasto  
#22 Posted : Friday, May 31, 2013 2:35:57 PM(UTC)
elasto

Joined: 8/23/2011(UTC)
Posts: 245

Thanks: 6 times
Was thanked: 12 time(s) in 11 post(s)
A Rectangle is a struct so by definition you can't use only just one. Even just any time it gets passed into a method a copy is created ^_^

Don't worry about it - it's only a few bytes anyhow :)
Offline internetfreak  
#23 Posted : Friday, May 31, 2013 6:07:54 PM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Yeah, I know that a rectangle is a struct :)
Maybe I should really stop to be too much worriying but I can't help, I want good and performant code, especially because I want to target mobile platforms so I have to watch the memory consumption a bit ;)
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Offline internetfreak  
#24 Posted : Monday, June 10, 2013 9:29:09 AM(UTC)
internetfreak

Joined: 12/19/2011(UTC)
Posts: 529

Thanks: 10 times
Was thanked: 16 time(s) in 15 post(s)
Ok, I played a bit more with Delta and I noticed something: I have 32*32px Sprites and ny map is 20*15 big. To fit completely on the screen, the window has to be 640*480px big. I set this size but it looks like 16px too small but normally it should fit.
Is this normal or is there a bug in the screen space because earlier it worked if I remember it right.
Mein Blog: www.internetfreak.net

- Inoffizieller DeltaEngine-Supporter und Tutorialschreiber -
Rss Feed  Atom Feed
Users browsing this topic
OceanSpiders 2.0
2 Pages12>
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.

Powered by YAF.NET | YAF.NET © 2003-2018, Yet Another Forum.NET
This page was generated in 0.724 seconds.