I'm currently working on a desktop game that can be played in both fullscreen, as well as windowed mode. When the game is windowed, I want to ensure that the window always maintains a certain aspect ratio, while still allowing the user to resize the window.
To describe the flow: after the user resizes the window, the width and height will adjust to snap to a pre-defined aspect ratio. An example of this is the game Stardew Valley. When you try to resize the window to a small enough size, after letting go of the mouse button, the window resizes (grows) to a pre-defined minimum size.
The approach I want to take is to detect when the user is finished resizing the window, and then set the window size manually. But I'm not sure how to detect that end "event."
This is rather easy to make:
Make sure that your windows size can be changed, by writing Window.AllowUserResizing = true;
Subscribe to the ClientSizeChanged event of the Window Property in Game. Window.ClientSizeChanged += Window_ClientSizeChanged;
In your event-method (in my case Window_ClientSizeChanged), check for the windowsize by using the Window.ClientBounds Property.
Set the new values of the Window, if needed. Unsubscribe during resize so your own resize does not create a StackOverflowException (because ApplyChanges() fires this event again) and resubscribe after calling ApplyChanges().
private void Window_ClientSizeChanged(object sender, System.EventArgs e)
{
Window.ClientSizeChanged -= Window_ClientSizeChanged;
graphics.PreferredBackBufferWidth = Window.ClientBounds.Width < 100 ? 100 : Window.ClientBounds.Width;
graphics.PreferredBackBufferHeight = Window.ClientBounds.Height < 100 ? 100 : Window.ClientBounds.Height;
graphics.ApplyChanges();
Window.ClientSizeChanged += Window_ClientSizeChanged;
}
And that's it. The event is now fired whenever the window recieves a resize (either by you, by the user or upon creation).
Stardew Valley was written in XNA and does mostly the same (it updates settings and recalculates lightmaps), but the main idea is the same.
Related
I have four buttons to select range of numbers. (clicked button should animate and stay scaled just to indicate which button is selected) I am trying to play animation on button click in unity. so when button is clicked, animation is played and returns to idle. But I do not want it to return to idle state instead I want it to remain at the last frame of on click animation clip(at last frame scale is 1.2). Loop time is unchecked.
I have assigned onclick animator play(string) in the inspector. Please help
also tried following code
public GameObject rangeBtn;
public Animator rangeBtnAnim;
private void Start()
{
if ((rangeBtn != null) && (rangeBtn.GetComponent<Animator>() != null))
{
rangeBtnAnim = rangeBtn.GetComponent<Animator>();
}
}
public void PlayAnimation()
{
rangeBtnAnim.Play("RangeSelectionButton_Pressed");
}
I would simply use this setup with Parameters
Make the PressedAnimation clip not Loop Time and use a bool parameter e.g. IsPressed. (One could also use triggers instead but in this case a bool is easier and saver - triggers stack ...)
Than the setup for the transitions:
Idle -> PressedAnimation
and PressedAnimation -> Idle
Than in your code instead you would set
rangeBtnAnim.SetBool("IsPressed", true);
or
rangeBtnAnim.SetBool("IsPressed", false);
to go back to idle.
Note the simplest way in fact of having animations (if they are not complex) would be to simply only have 1 single keyframe in Idle and also in PRessedAnimation. Unity would than simply make a transition animation interpolating between the values according to the transition duration settings.
Alternatively if your animations get more complex you would probably rather do something like
And for the two transitions
PressedAnimation -> StayPRessed
UnPressAnimation -> Idle
use the ExitTime
for the other two use the parameter
and
Something I used in my games. Let's say you have two animations: Idle and Animated.
You want your button, after Animated finishes, to stays on the last frame right ? Then you create a third animation, Animated_Remains, which contains only one frame, the last from Animated.
Then, in your Animation Controller, you put a transition between Animated (no loop) and Animated_Remains (looped). If you want the same behaviour when transiting from Animated to Idle, then you create a 4th anim, Idle_Remains.
So, after clicking on your button, it will play the Animated animation, and automatically transit to the Animated_Remains.
I did that when I had to move my Camera with an animation and make it stay at the last frame position.
I have a listview and a rectangle on top of it. The ListView's delegates have DropAreas in them and the rectangle has drag enabled on Y axis with its relevant properties (hotspot, target).
When I move the rectangle up and down on the ListView, the DropAreas of the delegates are registering onEntered signals. Working as intended.
However, I am using SmoothedAnimation to scroll the list up and down when the rectangle is at the most top and bottom coordinates (list.y = 0 and list.height). So the rectangle is not moving, and the DropAreas of the list's delegates are moving under it. In that case, the onEntered is not registered because there is no dragging, the rectangle is completely still and although its hotspot is entering and leaving the DropAreas, there is no interaction.
This is because dragging mechanic is sending events all the time when moving and any DropAreas it comes inside can register the event. In my case there is no dragging and therefore no events.
Question: Can drag events be manually activated? Can I somehow simulate drag?
At first, you should change the drag.target: parent to drag.target: this. In this way instead of dragging the parent item, you drag only the mouseArea. After that, you should grab an image from the item that you want to drag. The code is here:
Rectangle {
id: rec
width: 100
height: 100
Drag.active: dragArea.drag.active
Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.CopyAction
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: this
onPressed:{
parent.grabToImage(function(result) {
parent.Drag.imageSource = result.url
})
}
}
}
I have managed to induce drag events by manually changing the hotSpot property of the rectangle above the list. QML Drag documentation says:
Changes to hotSpot trigger a new drag move with the updated position.
So I have done this every time Listview contentY changes (vertical scrolling happens)
onContentYChanged:
{
rectangle.Drag.hotSpot.x += 0.0001
rectangle.Drag.hotSpot.x -= 0.0001
}
In hindsight, however, this is a 'hacky' solution. hotSpot property is a QPointF class property (link). This means it can be set using various methods (setX, setY, rx, ry..). For example, this:
rectangle.hotSpot += Qt.point(0,0)
or
rectangle.hotSpot = Qt.point(rectangle.hotSpot.x,rectangle.hotSpot.y)
should work in theory by resetting the hotSpot point when contentY changes, but testing revealed it unfortunately does not trigger a new drag move. (bug or intended, I don't know)
Now some of you that are more into Qt and QML might know more about this and how to properly adress the issue, but the above solution works for me and after testing everything I could imagine to get a cleaner solution, I settled on it.
i have a 2d platformer with a player. The players default sprite is set to idle. There is 4 sprites and states in total --> idle, jumping, left, right.
Im trying to get the player to switch sprites depending on what their doing eg jumping or moving right.
This is my first time using unity and animator so im not experienced at all.
So each of my animations look something like this. 1 frame and one for each state:
And then I open up the animator:
Im not too sure what the arrows do but i figured its like a flow chart so you go from idle to left so i matched up the arrows. I then also made 4 booleans for each state.
Then on the script attached to the player, i added this code:
private Animator animator;
void Start () {
animator = GetComponent<Animator>();
}
//This is in the update function
if (!playerLeft && !playerRight && !jumping)
{
setAnimationState("playerIdle");
}
else if (jumping)
{
setAnimationState("playerJumping");
}
}
private void setAnimationState(String state){
animator.SetBool("playerJumping", false);
animator.SetBool("playerLeft", false);
animator.SetBool("playerRight", false);
animator.SetBool("playerIdle", false);
animator.SetBool(state, true);
}
So i made a function that cancels of the animations and switches it to the correct one which is called. You can see how i call it with jumping and i also have the same for left and right but i know they are all being called correctly from the animator.
In the animator, only the right grey box is running. I can see because it has a blue bar and its not switching animations. The booleans are switching but thats it.
On the top of my head:
You need a transition from right to left and left to right.
You need one from right, left and jumping back to idle.
You can click on the arrows and select which booleans need to be checked to enable the transition.
Fore each animation state you need to upload a spritesheet. You can drag and drop this in the animation tab. The animation will play untill there are no more sprites. It will then check if it can transition into another state or loop the current state. (Do note that you need to enable looping on each state, except jumping i guess?)
Welcome to Unity development :)
I don't want to make this too long, so let's just use playerLeft as an example.
So you've set the Idle state as the default state - meaning if all the booleans are false, the player is idle. But once you press the left key, you would want the player to transition from the idle state to the Left state.
First step is to click on the transition (the arrow) from idle to playerLeft.
Next, is to add the playerLeft boolean as the condition. This means that when the playerLeft boolean becomes true, we start transitioning from Idle to Left.
And finally, tinker with the code. We need to tell Unity to set playerLeft to true when we press the "D" button for example.
Update()
{
if(Input.GetKeyDown(KeyCode.D))
animator.SetBool("playerLeft",true);
}
We place it in the Update() method to make sure it runs every frame. Then, we have a condition where it waits for the player to press D.
There's still a whole lot to do after this such as doing return transitions but well, you'll figure out the rest as you go so don't hesitate to ask ;)
I have a UIpanel in my application and i instantiate gameobject in it programmatically during runtime, the problem i have is i have to create more than 1000 gameobjects (sometimes up to 6000) in that panel and that takes a long time freezing the UI and trying it with Coroutines cause some lags as well.
is there any way to have a list of gameobjects and create them in UI panel during user scrolling that panel?
or can somebody suggest a better way of adding gameobjects to a panel without any lag or UI freeze?
UPDATE :
I added this to clear my goal.
i have a long list of items that i trying to put them in a list each item have a picture a some button and some texts, below there is a sample from a single item:
now i need to instantiate this complex sample in a panel and each item change itself based on a ID i pass after instantiation.
but i have a panel capacity problem and even if i cut some item when i put that many object in a panel at once scrolling in panel make it very slow and painful
also i need a scroll bar for my panel so i have to have all of my object available in the panel
hope this information helped.
When you describe it that way, its much clearer & much simpler.
Look at any mobile application: they do not put 6000 people records on a single page. Its normally paged or grouped, with a search option. So do that instead.
If you look at the facebook mobile app (a good standard) if you start scrolling to look at older posts, every few seconds it pauses for a second or two to load the next few posts. Its the same with the youtube app.
Because of this 65535 unity limit, just put in a paging limit (say 100-200 items).
Ok, I just wrote a test program which generated 1000 Text UI elements.
public class MakeLoadsOfUI : MonoBehaviour {
public GameObject prefab;
// Use this for initialization
void Start ()
{
MakeUI();
}
void MakeUI()
{
float startTime = Time.realtimeSinceStartup;
for (int i = 0; i < 1000; i++)
{
int x=i/40, y=i%40;
GameObject obj = (GameObject)Instantiate(prefab);
obj.transform.SetParent(transform); // attack to the canvas
RectTransform rt = obj.GetComponent<RectTransform>();
rt.anchoredPosition = new Vector2((x-10)*25, (y-10)*20);
obj.GetComponent<Text>().text = "UI:" + i;
}
float endTime = Time.realtimeSinceStartup;
Debug.Log("time taken:" + (endTime - startTime));
}
}
It took 0.15 seconds for 1000 elements which was quite reasonable. If you had a different result please elaborate, explaining how you did it.
In GTK (or pygtk or gtkmm...)
How can I detect that an application window has been manually resized by the user, as is typically done by dragging the window's edge?
I need to find a way to differentiate manual resizes from resizes that originate from gtk, such as changes in window content.
Have you tried connecting to the GDK_CONFIGURE event?
Check out this example under the
"Moving window" section. The example shows a callback doing something when the window is moved, but the configure event is a catch-all for moving, resizing and stack order events.
I managed to pull this off by watching for size_allocate and size_request signals on the GtkWindow. If size_request ever got smaller, I called resize(1,1). If size_allocate was ever bigger than expected, I turned the system off.
One thing I made sure to handle was size_request returning big, then small, and having size_allocate be big and then small. I don't know if this is possible, but I fixed it by making sure to only decrease the expected values for size_allocate when I got a smaller size_allocate, not when I got a smaller size_request.
Make sure that your size_request handler comes after the base class' handler so that you get the right values. I did this by overriding the method and then calling the base class method first.
I've tried this in both 1 and 2 dimensions and it seems to work either way.
In my case I was trying to distinguish between a user resizing a Gtk.Paned from the user resizing the whole window. Both emitted the notify::position signal.
My solution was, since I can't know if the user is resizing the window from the widget, reverse what I wanted to know. Record if the user has re-positioned the widget and ignore updates if the user didn't initiate them on my widget.
That is to say, instead of testing "if window being resized" I recorded the button-press-event and button-release-event's locally so I could instead test "if widget being re-positioned"
from gi.repository import Gtk
class MyPaned(Gtk.Paned):
_user_activated = False
def on_position(self, _, gparamspec):
if self._user_activated:
# widget touched
else:
# window resized (probably)
def on_button_press(self, *_):
self._user_activated = True
def on_button_release(self, *_):
self._user_activated = False
dev __init__(self, *args):
super(MyPaned, self).__init__(*args)
self.connect('notify::position', self.on_position)
self.connect('button-press-event', self.on_button_press)
self.connect('button-release-event', self.on_button_release)
Effectively by recorded when the user started and ended interacting with my widget directly, I could assume the rest of the time was due to the window being resized. (Until I find more cases)
In PyGTK, I've always watched for the expose_event for a window resize, then use the get_allocation method to get the new size.
You may be able to throw something together by using gdk_window_get_root_origin to get the top left corner of the window and gdk_window_get_geometry to get the width and height. Then you could hook a callback into the GDK_BUTTON_PRESS_MASK and check to see if the button press occurs near/on one of the edges of the window.
Of course, this seems quite hackish and it really bothers me that I couldn't find some simple way in the documentation for GdkWindow to do this. There is a gdk_window_begin_resize_drag function which really makes me think there's a cleaner way to do this, but I didn't see anything more obvious than my answer.