I am developing my own tile-based game in xna 4.0, and i suffer big fps lags while drawing the tiles on the screen.
I've read a lot about increasing performance and i've got to this:
I store my tiles in a simple dictionary
private Dictionary<Vector2, IBlock> WorldBlocks;
private Vector2 ExactBlockPosition;
private IBlock ExistingBlock;
public void Draw(SpriteBatch SpriteBatch, Vector2 ScreenStart, Vector2 ScreenEnd)
{
// Run over all the blocks in the screen
for (this.ExactBlockPosition.X = this.GetCurrentBlockXPosition(ScreenStart.X);
this.ExactBlockPosition.X < ScreenEnd.X;
this.ExactBlockPosition.X += Global.BLOCK_WIDTH)
{
for (this.ExactBlockPosition.Y = this.GetCurrentBlockYPosition(ScreenStart.Y);
this.ExactBlockPosition.Y < ScreenEnd.Y;
this.ExactBlockPosition.Y += Global.BLOCK_HEIGHT)
{
// Check if there is a block in the exact block location
if (this.WorldBlocks.TryGetValue(this.ExactBlockPosition, out this.ExistingBlock))
{
// Draw the block on the screen
this.ExistingBlock.Draw(SpriteBatch);
}
}
}
}
private int GetCurrentBlockXPosition(float X)
{
return (int)(X - X % Global.BLOCK_WIDTH);
}
private int GetCurrentBlockYPosition(float Y)
{
return (int)(Y - Y % Global.BLOCK_WIDTH);
}
In short, this code is drawing the blocks that are on the screen only.
But still my fps falls to 31 when there is a lot of tiles on the screen.
The tile width and heigh is 20 pixels.
Any suggestions why my fps falls?
Related
I'm trying to make a top-down shooter game in unity 2d. But the enemies are always clumping together. Does someone knows how to avoid it?
Here's my enemy code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float moveSpeed;
public float stoppingDistance;
public Transform player;
private Rigidbody2D rb;
public GameObject effect;
public int health = 3;
public static int enemyCounter;
public SpriteRenderer enemy;
public Color hurtColor;
void Start() {
rb = GetComponent<Rigidbody2D>();
player = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update() {
enemyCounter = EnemySpawner.enemyCounter;
Vector2 direction = transform.position - player.position;
if (Vector2.Distance(transform.position, player.position) > stoppingDistance) {
transform.position = Vector2.MoveTowards(transform.position, player.position,
moveSpeed * Time.deltaTime);
}
else if (Vector2.Distance(transform.position, player.position) > stoppingDistance) {
transform.position = this.transform.position;
}
}
IEnumerator Flash(){
enemy.color = hurtColor;
yield return new WaitForSeconds(0.01f);
enemy.color = Color.red;
}
void OnCollisionEnter2D(Collision2D other) {
if (other.gameObject.tag == "Bullet") {
StartCoroutine(Flash());
health -= 1;
}
if (health <= 0) {
GameObject DestroyEnemy = Instantiate(effect, transform.position, Quaternion.identity);
Destroy(this.gameObject);
Destroy(DestroyEnemy, 2f);
}
}
}
The enemies is moving towards the player, but they clump together when I move the player. I really need help.
If the enemies are moving in straight line to the player they will always clump up. If you want to ensure a minimal distance between enemies you could consider using a second larger collider on another layer but this will might end up looking bad and could make some exploits possible if too big.
The other alternative would be to change the behaviour of the enemies. Making an enemy move depending on the position of the other enemies seems complicated therefore i would simply try to add some randomness to their behaviour. Here are some ideas you could try:
-(would be my first try) switch randomly between two behaviours, 1: move straight to the player (always if very close to player), 2: move in a random direction (sometimes when further away from the player)
-don't give all your enemies the same speed
-make them move towards the player + a small random angle away from the player if far away
-...
To sum it up you will need an improved behaviour and there isn't a single solution.
I have 20 spites, I want them to be animated when a button is clicked.
Two types of animations 1) Position 2) Rotation.
Is there a recommended way to do this? Only way I can think of is recursively call setposition and angle with a delta value on Render method till the desired position and angle are reached.
When you have a start state and an end state, and you want to fill in the middle states, this is known as 'tweening (from inbetween). It comes from cartoon animation, but has come to be used more generally.
LibGDX makes use of Universal Tween Engine. You can start your journey to animating anything you want here. But, to give a bit more detail on how it works, here is an example from some of my own stuff. A similar usecase with regards to a sprite, but I have my sprites wrapped in a more generic class, a JJRenderNode. Here is how I make my class open to being tweened.
First you need a TweenAccessor for the class you want to tween.
public class RenderNodeTweenAccessor implements TweenAccessor<JJRenderNode> {
public static final int WIDTH = 1;
public static final int HEIGHT = 2;
public static final int WIDTH_HEIGHT = 3;
public static final int ALPHA = 4;
public static final int ALPHA_WIDTH_HEIGHT=5;
#Override
public int getValues(JJRenderNode target, int tweenType, float[] returnValues) {
switch (tweenType) {
case WIDTH:
returnValues[0] = target.getWidth();
return 1;
case HEIGHT:
returnValues[0] = target.getHeight();
return 1;
case WIDTH_HEIGHT:
returnValues[0] = target.getWidth();
returnValues[1] = target.getHeight();
return 2;
case ALPHA:
returnValues[0] = target.getColour().a;
return 1;
case ALPHA_WIDTH_HEIGHT:
returnValues[0] = target.getColour().a;
returnValues[1] = target.getWidth();
returnValues[2] = target.getHeight();
return 3;
default:
assert false;
return -1;
}
}
#Override
public void setValues(JJRenderNode target, int tweenType, float[] newValues) {
switch (tweenType) {
case WIDTH:
target.setWidth(newValues[0]);
break;
case HEIGHT:
target.setHeight(newValues[0]);
break;
case WIDTH_HEIGHT:
target.setWidth(newValues[0]);
target.setHeight(newValues[1]);
break;
case ALPHA:
target.getColour().a=newValues[0];
break;
case ALPHA_WIDTH_HEIGHT:
target.getColour().a=newValues[0];
target.setWidth(newValues[1]);
target.setHeight(newValues[2]);
default:
break;
}
}
}
The constant ints and the 'tweenType' in each of the get and set methods let you tween more than one combination of fields. In this case I have different combinations of width, height and alpha values for my JJRenderNode.
You have to register this TweenAccessor as follows:
Tween.registerAccessor(JJRenderNode.class, new RenderNodeTweenAccessor());
And then you are free to tween your class, for example:
Timeline.createSequence()
.push(Tween.set(node, RenderNodeTweenAccessor.WIDTH_HEIGHT).target(START_WIDTH, START_WIDTH))
.push(Tween.to(node, RenderNodeTweenAccessor.WIDTH_HEIGHT, 0.4f).target(PuzzleBlockCore.MAX_RENDER_WIDTH, PuzzleBlockCore.MAX_RENDER_WIDTH))
.start(JJ.tweenManager);
PS. You also need an instance of a TweenManger, and this needs to be updated with delta for each gameloop. I have a 'singleton' global instance that I use everywhere (JJ.TweenManager).
You might as well use spine 2d for animation. It costs, but it is worth it. For $60 I think, you get full libgdx support + bone rigging and animation.
http://esotericsoftware.com/
If you do want to animate with only libgdx however, you can create a list with all your sprite animations frames, loop through, and switch sprite texture to next frame in the animation.
private void render() {
sprite.set(aninationframes.get(currentFrame)
currentFrame = currentFrame + 1}
Though, you may want to add a delay per frame.
If(current time - time > some value) {
sprite.set(animationframes.get(currrntFrame)
currentFrame = currentFrame + 1
time = get current time here
}
I have a quite simple unity GUI that has the following scheme :
Where Brekt and so are buttons.
The GUI works just fine on PC and is on screen space : overlay so it is supposed to be adapted automatically to fit every screen.
But on tablet the whole GUI is smaller and reduced in the center of the screen, with huge margins around the elements (can't join a screenshot now)
What is the way to fix that? Is it something in player settings or in project settings?
Automatically scaling the UI requires using combination of anchor,pivot point of RecTransform and the Canvas Scaler component. It is hard to understand it without images or videos. It is very important that you thoroughly understand how to do this and Unity provided full video tutorial for this.You can watch it here.
Also, when using scrollbar, scrollview and other similar UI controls, the ContentSizeFitter component is also used to make sure they fit in that layout.
There is a problem with MovementRange. We must scale this value too.
I did it so:
public int MovementRange = 100;
public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use
public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input
public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input
private int _MovementRange = 100;
Vector3 m_StartPos;
bool m_UseX; // Toggle for using the x axis
bool m_UseY; // Toggle for using the Y axis
CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input
CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input
void OnEnable()
{
CreateVirtualAxes();
}
void Start()
{
m_StartPos = transform.position;
Canvas c = GetComponentInParent<Canvas>();
_MovementRange = (int)(MovementRange * c.scaleFactor);
Debug.Log("Range:"+ _MovementRange);
}
void UpdateVirtualAxes(Vector3 value)
{
var delta = m_StartPos - value;
delta.y = -delta.y;
delta /= _MovementRange;
if (m_UseX)
{
m_HorizontalVirtualAxis.Update(-delta.x);
}
if (m_UseY)
{
m_VerticalVirtualAxis.Update(delta.y);
}
}
void CreateVirtualAxes()
{
// set axes to use
m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal);
m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical);
// create new axes based on axes to use
if (m_UseX)
{
m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis);
}
if (m_UseY)
{
m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis);
}
}
public void OnDrag(PointerEventData data)
{
Vector3 newPos = Vector3.zero;
if (m_UseX)
{
int delta = (int)(data.position.x - m_StartPos.x);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.x = delta;
}
if (m_UseY)
{
int delta = (int)(data.position.y - m_StartPos.y);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.y = delta;
}
transform.position = new Vector3(m_StartPos.x + newPos.x, m_StartPos.y + newPos.y, m_StartPos.z + newPos.z);
UpdateVirtualAxes(transform.position);
}
I have searched and found nothing. I am trying to get a simple bouncing ball to speed up on a continuous basis. The code below does not speed the ball up at all (even with a mouse click), however the getRate() property does increase as expected. If I comment out the increaseSpeed() method call in moveBall() the mouse click will speed up the ball.
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
public class MyApp extends Application {
#Override
public void start(Stage primaryStage) {
Bounce bouncePane = new Bounce();
bouncePane.setOnMousePressed(e -> bouncePane.increaseSpeed());
Scene scene = new Scene(bouncePane, 250, 150);
primaryStage.setTitle("Bouncing Ball");
primaryStage.setScene(scene);
primaryStage.show();
bouncePane.requestFocus();
}
}
public class Bounce extends Pane {
public final double rad = 25;
private double x = rad, y = rad;
private double dx = 1, dy = 1;
private Circle ball = new Circle(x, y, rad);
private Timeline anim;
public Bounce() {
ball.setFill(Color.BLUE);
getChildren().add(ball);
anim = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
anim.setCycleCount(Timeline.INDEFINITE);
anim.play();
}
public void increaseSpeed() {
anim.setRate(anim.getRate() + 0.5);
System.out.println(anim.getRate());
}
protected void moveBall() {
if (x < rad || x > getWidth() - rad) {
dx *= -1;
}
if (y < rad || y > getHeight() - rad) {
dy *= -1;
}
x += dx;
y += dy;
ball.setCenterX(x);
ball.setCenterY(y);
increaseSpeed();
}
}
Your application actually works (kinda)
If you set the duration of your KeyFrame to Duration.millis(1_000) instead of Duration.millis(50), then you will see the animation speed up (well up to a factor of 1_000/60).
Huh? What's going on
By default JavaFX is capped at 60 frames per second. The key frames in your timeline will not be invoked any more often than that, no matter what you set the playback rate to. Because in your example you set the initial duration of the animation to 50 milliseconds, then increase rate by 50 percent of the original rate in each frame, the animation callback for the key frame quickly (in a fraction of time imperceptible to the human eye) reaches the maximum frame rate of 60 frames per second and once it does that, you can't run it any faster.
What you can do
For this kind of problem, perhaps don't rely on animation playback rate to control the speed of your object. Instead, implement a "game loop" using an AnimationTimer and associate a velocity with your objects and move the object to the required position based upon the loop such as is done in this AnimationTimerTest by JamesD.
I am coding a game with friends and we have problems to get the same movement on our different PC. In Slick2D, we use the Delta to make the movement :
if (input key up)
{
sprite = up; // changing the sprite
if (no collision){
sprite.update(delta*3); //animation
y -= delta * 0.3f; // movement
}
}
The problem is that delta is the time between two frames. And this time in not the same on each computer because of performance. So the slower the computer is, the slower the movement will be.
How to solve this problem ?
Thank you for helping us, and I hope you understand my english !
How about setting a target frame rate for your application? It can normalize the speed of movement throughout all computers (given the computer can reach that rate). So whenever you first instantiate your game class, do this:
private static final int TARGET_FRAME_RATE = 60;
private static final int WINDOW_WIDTH = 640;
private static final int WINDOW_HEIGHT = 480;
public static void main(String[] args) {
try {
AppGameContainer appgc = new AppGameContainer(new MyGame());
appgc.setDisplayMode(WINDOW_WIDTH, WINDOW_HEIGHT, false);
// Set the target framerate for the appgc
appgc.setTargetFrameRate(TARGET_FRAME_RATE);
appgc.start();
} catch (SlickException e) {
System.err.println("There was an error executing the game...");
e.printStackTrace();
}
}