MonoGame - WinForm for homescreen? - forms

I'm starting a project with monogame. I've made games with MonoGame before, but I felt the switching between multiple forms and monogame was clunky. For example I had a homescreen with buttons to Play, Hiscores & Quit. So I had forms for the homescreen and the hiscores. And then when you clicked Play the actualy MonoGame game would open.
Am I supposed to create everything in MonoGame? Or do I use panels in a single Form?

What I did to make it easier for myself is to make a class called "Screen", and just make a List<Screen> that holds all the different screens, so now, all I have to do to switch screen is set "Visible" to false or true on the screens I want to show or hide.
Here is part of how that class looks
public class Screen
{
public int ID;
public string Name;
public List<Form> Forms = new List<Form>();
public List<Label> Labels = new List<Label>();
public List<Button> Buttons = new List<Button>();
public List<Textbox> Textboxes = new List<Textbox>();
public List<EViewport> Viewports = new List<EViewport>();
public bool Visible;
// and a bunch of functions to add forms, buttons, do events, etc
// when adding a control, it's important that it is set to the right viewport
public Button AddButton(int _ID, string _Name, Texture2D _Sprite, int _Width, int _Height, Vector2 _Position, EViewport _Viewport, string _Text = "", bool _Visible = false)
{
Buttons.Add(new Button(_ID, _Name, _Sprite, _Width, _Height, new Vector2(_Position.X, _Position.Y), _Text, _Visible));
Button getButton = Buttons[Buttons.Count - 1];
getButton.ParentScreen = this;
getButton.ParentViewport = _Viewport;
return getButton;
}
public void Draw(SpriteBatch _spriteBatch, SpriteFont font, GameTime gameTime, Textbox focusedTextbox)
{
if (Visible)
{
// Draw only if screen is visible (this is not the full Draw function btw)
for(int j = 0; j < Viewports.Count; j++)
{
for(int i = 0; i < Buttons.Count; i++)
{
if(Buttons[i].ParentViewport.ID == Viewports[j].ID) // then draw
}
}
}
}
}
So basically all I do is add buttons, forms and viewports (because on my main menu I only need one viewport, but in-game I need 3 different viewports (one for game screen, one for UI and one for the chat) and then change their visibility like this:
storage.GetScreenByName("GameScreen").Visible = true;
(storage is just a singleton class that holds the sprite objects and screens in my game)
Oh, by the way, here's my EViewport class:
public class EViewport
{
/* Extended Viewport */
public Viewport _Viewport;
public Screen ParentScreen = new Screen();
public int ID;
public string Name;
public bool AllowResize;
public int MaxWidth;
public int MaxHeight;
public int MinHeight;
public float AspectRatio { get { return (float)MaxWidth / (float)MaxHeight; } }
public EViewport()
{
ID = -1;
}
public EViewport(int _ID, string _Name, int x, int y, int width, int height, bool _AllowResize = false, int _MinHeight = 0)
{
ID = _ID;
Name = _Name;
_Viewport = new Viewport(x, y, width, height);
AllowResize = _AllowResize;
MinHeight = _MinHeight;
MaxWidth = width;
MaxHeight = height;
}
public bool HasParentScreen()
{
return (ParentScreen.ID != -1);
}
}
I hope this helps, feel free to ask questions regarding how to implement such a system.

Related

Unity: How to make a scrollrect fit its content with a maximum height?

I have a Container (1) that I want to grow with its content.
Inside this Container (1), I have a Scrollrect (2) that I also want to grow with its content but with max height.
The goal is that if there is only one line of text in (2), (1) will be much smaller. When a lot of text is added, (2) will grow until its max height is reached and the scrollbar will take over.
I have been at it for hours and I cannot seem to find a way to make it work.
You can do this via Text.preferredHeight e.g. like this component on your scroll content
public class ContentScaler : MonoBehaviour
{
[SerializeField] private RectTransform _scrollRectTransform;
[SerializeField] private RectTransform _contentRectTransform;
[SerializeField] private Text _text;
[SerializeField] private float minScrollHeight;
[SerializeField] private float maxScrollHeight;
private void Awake()
{
if (!_contentRectTransform) _contentRectTransform = GetComponent<RectTransform>();
}
private void Update()
{
// Get the preferred Height the text component would require in order to
// display all available text
var desiredHeight = _text.preferredHeight;
// actually make your text have that height
var textSizeDelta = _text.rectTransform.sizeDelta;
textSizeDelta.y = desiredHeight;
_text.rectTransform.sizeDelta = textSizeDelta;
// Then make the content have the same height
var contentSizeDelta = _contentRectTransform.sizeDelta;
contentSizeDelta.y = desiredHeight;
_contentRectTransform.sizeDelta = contentSizeDelta;
var scrollSizeDelta = _scrollRectTransform.sizeDelta;
scrollSizeDelta.y = Mathf.Clamp(desiredHeight, minScrollHeight, maxScrollHeight);
_scrollRectTransform.sizeDelta = scrollSizeDelta;
}
}
Building upon Ryan Pergent's answer, I made the code do what he wanted (Mostly) and added the ability to set the preferred height and width
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class AdaptablePreferred : LayoutElement, ILayoutElement {
#if UNITY_EDITOR
[CustomEditor(typeof(AdaptablePreferred))]
public class Drawer : Editor {
public override void OnInspectorGUI() {
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
EditorGUI.EndDisabledGroup();
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Height");
var prop = serializedObject.FindProperty("_usePreferredHeight");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredHeightMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Preferred Width");
prop = serializedObject.FindProperty("_usePreferredWidth");
EditorGUILayout.PropertyField(prop, new GUIContent(""), GUILayout.Width(EditorGUIUtility.singleLineHeight + 2));
if(prop.boolValue) {
var tmp = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 35;
EditorGUILayout.PropertyField(serializedObject.FindProperty("_preferredWidthMax"), new GUIContent("Max"));
EditorGUIUtility.labelWidth = tmp;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(serializedObject.FindProperty("_contentToGrowWith"));
if(EditorGUI.EndChangeCheck()) {
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
[SerializeField] private RectTransform _contentToGrowWith;
[SerializeField] private bool _usePreferredHeight;
[SerializeField] private float _preferredHeightMax;
[SerializeField] private bool _usePreferredWidth;
[SerializeField] private float _preferredWidthMax;
private float _preferredHeight;
private float _preferredWidth;
public override float preferredHeight => _preferredHeight;
public override float preferredWidth => _preferredWidth;
public override void CalculateLayoutInputVertical() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredHeight) {
var height = LayoutUtility.GetPreferredHeight(_contentToGrowWith);
_preferredHeight = _preferredHeightMax > height ? height : _preferredHeightMax;
} else {
_preferredHeight = -1;
}
}
public override void CalculateLayoutInputHorizontal() {
if(_contentToGrowWith == null) {
return;
}
if(_usePreferredWidth) {
var width = LayoutUtility.GetPreferredWidth(_contentToGrowWith);
_preferredWidth = _preferredWidthMax > width ? width : _preferredWidthMax;
} else {
_preferredWidth = -1;
}
}
}
I achieved it by adding the following script to the Viewport:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(LayoutElement))]
public class AdaptablePreferredHeight : UIBehaviour, ILayoutElement
{
public float MaxHeight = 100;
public RectTransform ContentToGrowWith;
public int LayoutPriority = 10;
LayoutElement m_LayoutElement;
float m_Preferredheight;
public float minWidth => m_LayoutElement.minWidth;
public float preferredWidth => m_LayoutElement.preferredWidth;
public float flexibleWidth => m_LayoutElement.flexibleWidth;
public float minHeight => m_LayoutElement.minHeight;
public float preferredHeight => m_Preferredheight;
public float flexibleHeight => m_LayoutElement.flexibleHeight;
public int layoutPriority => LayoutPriority;
public void CalculateLayoutInputHorizontal()
{
if (m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
}
public void CalculateLayoutInputVertical()
{
if(m_LayoutElement == null)
{
m_LayoutElement = GetComponent<LayoutElement>();
}
float contentHeight = ContentToGrowWith.sizeDelta.y;
if (contentHeight < MaxHeight)
{
m_Preferredheight = contentHeight;
}
else
{
m_Preferredheight = MaxHeight;
}
}
}
I would have just inherited LayoutElement instead of making it a RequireComponent, but then the inspector won't show my new MaxHeight and ContentToGrowWith variables.
The ContentToGrowWith variable points to the content object inside the ViewPort.
The final setup is:
Scroll View with an horizontal layout group Control Child Size: "Height". (no child force expand, so it can shrink with its children)
Viewport with LayoutElement and my script AdaptablePreferredHeight.
Content with ContentSizeFitterset on Vertical Fit: "Preferred Size"

Unity2D: Scaling image in scene window

I have this script:
public Sprite[] Images; //Index starts at one because we are setting the first sprite in Start() method
public int _Index = 1;
public float width;
public float height;
void Start(){
//Set the image to the first one
this.gameObject.GetComponent<SpriteRenderer>().sprite = Images[0];
}
public void onClick(){
//Reset back to 0 so it can loop again if the last sprite has been shown
if (_Index >= Images.Length)
_Index = 0;
//Set the image to array at element index, then increment
this.gameObject.GetComponent<SpriteRenderer>().sprite = Images[_Index++];
}
that works perfectly, what it does is that it changes the image of and object OnClick once all images has been shown it loops. However I can't scale my images manually in my scene window nor through script. Is there an easier way of changing my script to be able to manually scaling my images in my scene window. (I would rather scale my images in my scene window rather than through script because I would find it easier) Please and thank you :)
Second Edit
This is what my script looks like now:
public Sprite[] Images;
//Index starts at one because we are setting the first sprite in Start() method
public int _Index = 1;
public float width;
public float height;
public float lastWidth;
public float lastHeight;
void ResizeMe()
{
this.width = 1.567892f;
this.height = 1.07f;
}
void Update()
{
if(this.width != lastWidth || this.height != lastHeight)
{
ResizeMe (this.width, this.height);
this.lastWidth = width;
this.lastHeight = height;
}
}
void Start(){
//Set the image to the first one
this.gameObject.GetComponent<SpriteRenderer>().sprite = Images[0];
}
public void onClick(){
//Reset back to 0 so it can loop again if the last sprite has been shown
if (_Index >= Images.Length)
_Index = 0;
//Set the image to array at element index, then increment
this.gameObject.GetComponent<SpriteRenderer>().sprite = Images[_Index++];
}
Thank you :)
declare 2 more variables
lastWidth
lastHeight
in the Update() function do something like
if(this.width != lastWidth || this.height != lastHeight)
{
setSize(this.width,this.height)
this.lastWidth = width;
this.lastHeight = height;
}
create a setSize function to do the actual resizing.
Now when you change the public width,height variables from the editor the update will pick on the change and run your resize script

Align variables horizontally in unity inspector

I'm writing a very simple class for storing data (at least, for now) for use in a Unity project to do with a learning AI. I want to be able to easily customize multiple agents in the inspector and the number of checkboxes stacked vertically makes this part of my project dominate the inspector. If I could simply have one section make use of the ample empty space on the right side of the inspector, that would be considerably less ugly.
I've been reading a lot about custom property drawers and inspector windows, but it seems like a lot of work, involving rewriting how the entire class is displayed, for one change.
For reference, here is the class itself.
[System.Serializable]
public class NNInfo
{
public string networkName = "Agent";
[Header("Movement Properties")]
public float movementSpeed = 10f;
public float rotationSpeed = 1f;
[Header("Learning Properties")]
public float learningRate = 0.1f;
public float momentum = 0f;
public Rewards rewardFunc;
public int[] hiddenLayers;
[Header("Other Properties")]
public int maxHealth = 1000;
[Header("Optional Inputs")]
public bool m_PointToNearestBall = false; // input which is 1 while the agent is facing a ball and -1 when facing away
public bool m_DistanceToNearestBall = false; // input which is 1 while the agent is at the ball and -1 when at max possible distance away
public bool m_PointToEndzone = false; // similar to m_PointToNearestBall but for the endzone
public bool m_Position = false; // two inputs which inform the player of its normalized x and y coordinates on the field
public bool m_WhetherHoldingBall = false; // tells the player whether its holding a ball
public bool m_CanSeeHealth = false; // Whether the player can know its own health
[Header("Optional Outputs")]
public bool m_ForwardLeft = false; // turn left and move forward simultaneously
public bool m_ForwardRight = false; // turn right and move forward simultaneously
public bool m_Reverse = false; // move backwards
public bool m_Flip = false; // instantly switch to opposite direction
public bool m_TurnToBall = false; // instantly turn to face nearest ball
public bool m_TurnToLeft = false; // instantly turn to face left side of field
public bool m_Attack = false; // attack a player (or idle if no contact)
}
Custom Property Drawer is keyword which should be interesting for you.
Writing your own code for managing those - lets you describe how you want to show your properties inside Inspector View in Unity editor.
To start, go to official documentation site, which contains code you can base on.
Code snippets (Javacrpt, c# version can be found under the link):
Object's code:
enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class
class Ingredient extends System.Object {
var name : String;
var amount : int = 1;
var unit : IngredientUnit;
}
var potionResult : Ingredient;
var potionIngredients : Ingredient[];
function Update () {
// Update logic here...
}
Editor code:
#CustomPropertyDrawer(Ingredient)
class IngredientDrawer extends PropertyDrawer {
// Draw the property inside the given rect
function OnGUI (position : Rect, property : SerializedProperty, label : GUIContent) {
// Using BeginProperty / EndProperty on the parent property means that
// prefab override logic works on the entire property.
EditorGUI.BeginProperty (position, label, property);
// Draw label
position = EditorGUI.PrefixLabel (position, GUIUtility.GetControlID (FocusType.Passive), label);
// Don't make child fields be indented
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
// Calculate rects
var amountRect = new Rect (position.x, position.y, 30, position.height);
var unitRect = new Rect (position.x+35, position.y, 50, position.height);
var nameRect = new Rect (position.x+90, position.y, position.width-90, position.height);
// Draw fields - passs GUIContent.none to each so they are drawn without labels
EditorGUI.PropertyField (amountRect, property.FindPropertyRelative ("amount"), GUIContent.none);
EditorGUI.PropertyField (unitRect, property.FindPropertyRelative ("unit"), GUIContent.none);
EditorGUI.PropertyField (nameRect, property.FindPropertyRelative ("name"), GUIContent.none);
// Set indent back to what it was
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty ();
}
}

How to get the index of an item at a specific offset from the top in Android ListView

Is there a simple way for getting the index of a list item which is in a specific offset from the top?
For example, get the index of an item which appears 150 pixels from the top.
Since your goal is to find which list item is in the center of the screen, you could try something like the following:
(Make a custom class that extends ListView, such as MyListView.java)
public class MyListView extends ListView implements OnScrollListener {
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnScrollListener(this);
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// Center the child currently closest to the center of the ListView when
// the ListView stops scrolling.
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
int listViewCenterX = getWidth() / 2;
int listViewCenterY = getHeight() / 2;
Rect rect = new Rect();
// Iterate the children and find which one is currently the most
// centered.
for (int i = 0; i < getChildCount(); ++i) {
View child = getChildAt(i);
child.getHitRect(rect);
if (rect.contains(listViewCenterX, listViewCenterY)) {
// this listitem is in the "center" of the listview
// do what you want with it.
final int position = getPositionForView(child);
final int offset = listViewCenterY - (child.getHeight() / 2);
break;
}
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
}
You can iterate through children and check each ones hit rectangle against the point you have.

Custom ProgressBar widget

I am trying to do something similar to this but in Android.
In Android I can extend the ProgressBar but I am doubting of how to add the TextViews on top. In iphone it was easy because I can use absolute positions, but not here.
Any ideas?
EDIT:
I decided to use SeekBar instead of ProgressBar to add the thumb drawable. I commented below. Some points to notice:
I am using hardcoded values, actually three but it can be more or less.
When the thumb is moved it moves to 50 but it should move to the different options.
I am using pixels instead of dpi. I should fix that.
I need to solve the lack of animation when the thumb moves.
My progress so far:
public class SliderFrameLayout extends FrameLayout implements OnSeekBarChangeListener {
private SeekBar mSlider;
private Context mContext;
private int mSize = 3;
private TextView[] mTextViews;
private String[] mTexts = {"Nafta", "Gas", "Gasoil"};
public SliderFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setWillNotDraw(false);
mTextViews = new TextView[mSize];
addSlider();
addTextViews();
}
private void addTextViews() {
for ( int i=0 ; i < mSize ; i++ ) {
TextView tv;
tv = new TextView(mContext);
tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
tv.setText(mTexts[i]);
mTextViews[i] = tv;
addView(tv);
}
}
private void addSlider() {
FrameLayout fl = new FrameLayout(mContext, null);
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
fl.setLayoutParams(params);
fl.setPadding(30, 30, 30, 0);
mSlider = new SeekBar(mContext, null);
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
//lp.setMargins(30, 30, 30, 0);
//mSlider.setPadding(30, 30, 30, 0);
mSlider.setLayoutParams(lp);
//mSlider.setMax(mSize-1);
mSlider.setThumbOffset(30);
//mSlider.setProgressDrawable(mContext.getResources().getDrawable(R.drawable.slider_track));
//mSlider.setThumb(mContext.getResources().getDrawable(R.drawable.slider_thumb));
mSlider.setOnSeekBarChangeListener(this);
//addView(mSlider);
fl.addView(mSlider);
addView(fl);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rectf = new Rect();
mSlider.getLocalVisibleRect(rectf);
Log.d("WIDTH :",String.valueOf(rectf.width()));
Log.d("HEIGHT :",String.valueOf(rectf.height()));
Log.d("left :",String.valueOf(rectf.left));
Log.d("right :",String.valueOf(rectf.right));
Log.d("top :",String.valueOf(rectf.top));
Log.d("bottom :",String.valueOf(rectf.bottom));
int sliderWidth = mSlider.getWidth();
int padding = sliderWidth / (mSize-1);
for ( int i=0 ; i < mSize ; i++ ) {
TextView tv = mTextViews[i];
tv.setPadding(i* padding, 0, 0, 0);
}
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.d("SEEK", "value: " + seekBar.getProgress());
seekBar.setProgress(50);
}
}
You're already overriding onDraw, why not just draw the text strings yourself? Rather than go through the overhead of adding TextViews and messing with the padding, just use canvas.drawText to physically draw the text strings in the right place.
You can specify the style and color of the text using a Paint object:
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(r.getColor(R.color.text_color));
textPaint.setFakeBoldText(true);
textPaint.setSubpixelText(true);
textPaint.setTextAlign(Align.LEFT);
And get the exact positioning by using the measureText method on that Paint object to find what width a particular string would be when drawn on a canvas:
textWidth = (int)textPaint.measureText(mTexts[i]);
Then you can iterate over your array of text strings and draw each string in the right place.
#Override
protected void onDraw(Canvas canvas) {
int myWidth = getMeasuredWidth()-LEFT_PADDING-RIGHT_PADDING;
int separation = myWidth / (mSize-1);
for (int i = 0; i++; i < mSize) {
int textWidth = (int)textPaint.measureText(mTexts[i]);
canvas.drawText(mTexts[i],
LEFT_PADDING+(i*separation)-(int)(textWidth/2),
TOP_PADDING,
textPaint);
}
}
You'll probably want to do the measurements in onMeasure instead of onDraw, and you should probably only measure the width of each string when you change the text or the paint, but I've put it all in one place to (hopefully) make it easier to follow.
Just a try. You could put your custom progressBar inside a FrameLayout then, inside this layout, you have to add three TextViews with fill_parent as width.
Then you can align the three texts in this way: left, center and right. Your texts shouldn't overwrite and you can adjust a little their position using margins.
You can use this... not exact what you looking for... but really easy to set up and customize...
You can set the TextViews to have:
android:layout_width="0dp"
android:layout_weight="1"
This will let the android framework take care of the placing of the TextViews and save you from manually calculating the padding of each one.