Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
KillHour
Oct 28, 2007


This is more of an "I did a thing" post than anything else, but this is the first time I've implemented the basic framework of a game idea and am having more fun playing with the result than I did making it so I figured I'd show it off a little.

The idea was "What if Factorio had a baby with Shenzhen I/O?" The Factorio part isn't there yet, but the coding part is starting to come into its own:

(Ignore the completely temporary and trademark-infringing coder art)



This is your AI core. It is your representation in and how you interact with the world.

You do so by assigning keypresses to 'signals' - information that carries a string (type) and integer (value) and can propagate between connected modules.



For instance, this is a light module connected to the AI core which has been programmed to output a "green" signal type of value "100" when the spacebar is pressed.



This is a CPU module. It runs code.



The CPU module can also modify and pass signals between other modules, allowing for things like this working spaceship (the things on the sides are thrusters and the lights indicate thruster output on that side):





Things that work so far:

- Loops

- Branching

- Most basic math operations

- Attaching and removing modules with code
https://www.youtube.com/watch?v=WVUchprqT38

- Code execution via signals and modifying code on remote modules
https://www.youtube.com/watch?v=hQZBD_p4wc4

- Robots that drag/throw things around and play a very rudimentary form of "catch" (they can't aim yet).

Future stuff:

- Mining, storing and refining materials

- Modules that build other modules

- Sensors that detect nearby items

- Ability to create clusters of modules that fully automate exploring, mining, building, and programming

- The need to manage power and heat with maybe sort-of realistic heat buildup and transfer mechanisms

- Ultimately, the ability to take over the entire universe with self-replicating robots of death CPU-destroying lag

It's obviously a long way off of that, but there's a pretty diverse set of programming tools already at your disposal:



I'm probably getting to the point where I could use a few people to play around with it and give me feedback as it evolves.

KillHour fucked around with this message at 00:30 on Jun 16, 2017

Adbot
ADBOT LOVES YOU

KillHour
Oct 28, 2007


I can help a little bit with the polynomials. A polynomial (literally multiple numbers) is most commonly used to define a special type of curve on a graph. The simplest kind is a binomial: y = 2x+c where X and Y are coordinates and c is some constant (literally, you can just make one up. Like 65 or -5.3 or pi or the square root of 2). Throw it in Wolfram Alpha to give it a try.

All polynomials follow a standard format, which is:

y = c1*xn+c2*xn-1+...+cm*x1+cm+1*x0

Let's break that down. Basically, you have a bunch of x raised to some power and multiplied by some constant and you add all those together. For example:

4x3+7.3x2-2x+5

Then you just plug your x coordinate in and that gives you the y coordinate for that point on the curve.

Higher order polynomials means you just have more groups of this (ie X is raised to a higher power in the first group).

Splines are just multiple polynomial curves attached to each other at the ends so play around with polynomials a lot before trying to grasp them and it will help.

KillHour fucked around with this message at 16:02 on Jun 16, 2017

KillHour
Oct 28, 2007


Tiler Kiwi posted:

To be fair, being recited a dictionary does essentially describe the quality of my primary school math education. :v:

I'm taking a step back and finding better instructions on the subject matter, though. I get stubbornly fixated when I get frustrated, which is sometimes helpful, very often not.

I know what derivatives are, now, at least! Kind of important thing to know, when it comes to describing change at a point.


Thank you. It helps clear up a fundamental misunderstanding I was having over what a MathNet spline is; I kept wanting it to be a thing I could just feed some points and get back a thing I could get a bunch of points out of, but I suppose I'll need to implement some of that myself.

Anything calculus (i.e. derivitives) is going to use polynomials as its basis, and therefore pretty much all of physics will as well. So I really suggest learning polynomials and factoring and algebra like the back of your hand. It will help you with everything.

KillHour
Oct 28, 2007


Tiler Kiwi posted:

I kept wanting it to be a thing I could just feed some points and get back a thing I could get a bunch of points out of, but I suppose I'll need to implement some of that myself.

I did this for a tower defense game I made where I wanted the units to smoothly follow a bunch of waypoints. Instead of trying to find a mathematical curve that best fit the points through some kind of regression, I just gave each waypoint a position and marched from one to the next using this:

code:
moveDirection = (currentWaypoint.transform.position - transform.position);
lookDirection = Quaternion.LookRotation (moveDirection, Vector3.up);
transform.rotation = Quaternion.Lerp (transform.rotation, lookDirection, (2 / moveDirection.magnitude) * speed * Time.fixedDeltaTime);
transform.position += transform.rotation * Vector3.forward * speed * Time.fixedDeltaTime;
You're not using Unity, so I'l break it down a little bit. Each "step" the unit takes, I have them rotate a little bit towards the next waypoint and then take a step forward. This is accomplished with the Quaternion.Lerp function, which returns an interpolation between the two rotations.

https://docs.unity3d.com/ScriptReference/Quaternion.Lerp.html

Stepping through this gives a very "river-like" path where the units will always pass exactly through each waypoint without making any abrupt turns.



The green circles are waypoints and the red line is the path units take following them.

Edit: I removed all but 5 waypoints and moved them around to show you don't need a ton of points to get something smooth and river-like.

KillHour fucked around with this message at 18:53 on Jun 16, 2017

KillHour
Oct 28, 2007


Well, I can strike "sensors and the ability to detect nearby objects" off my list.

https://www.youtube.com/watch?v=Yxua-wFnOZA

Next up is probably some sort of resource collection. Most likely starting with figuring out how I'm going to handle inventory and moving objects around.

Edit: And a bonus one of several flying in formation.

https://www.youtube.com/watch?v=EMLAUdO3Ix4

KillHour fucked around with this message at 05:02 on Jun 17, 2017

KillHour
Oct 28, 2007


Buy a D-Wave and use quantum annealing. :haw:

KillHour
Oct 28, 2007


gently caress serialization. That is all. :bang:

KillHour
Oct 28, 2007


The problem is none of the classes in unity support constructors, so you either need to make intermediary classes or write your own deserialization from scratch.

KillHour
Oct 28, 2007


Deserializing usually means creating the classes with a default constructor and restoring the values. If you can't use that, then built-in deserializers can't do their job. You either need to create an intermediary class for everything that can be directly constructed and then transfer all that data over, or - the way I did it - create the classes yourself (via instantiation in Unity) and then parse the serialized data manually and insert the values. This also means you either can't use the Start/Awake callbacks to do anything that requires the serialized data as a prerequisite or you need to have another delegate that overwrites what they do. Also, I had to go back through and remove almost all the co-routines and replace them with normal functions that store their state as public properties.

It's just a PITA that there are so many features that Unity goes "Go ahead and use these!" but don't actually work if you need to persist that data. It's a huge gotcha for people not experienced with Unity and it sucks.

KillHour
Oct 28, 2007


Can you give us a more concrete example of what you're trying to do with this? You can do this with reflection, but it seems like a lot of hoops to jump through.

https://dotnetfiddle.net/RtFDfB

KillHour
Oct 28, 2007


Stick100 posted:

Unless you personally care about learning A*, I'd suggest using the standard well review asset in the Asset store. No reason to reinvent the wheel.

On the other hand, implementing A* is a great introduction to graph theory. Once I did it once, I was much more comfortable using graphs to solve problems in programming - I find I use them all the time and they're applicable in a ton of areas.

KillHour
Oct 28, 2007


Unity's 2D text doesn't have an easy way to make it ignore z testing when in world space and Unity's 3D text doesn't have an easy way to do outlines or word wrap. :bang:

KillHour
Oct 28, 2007


Stick100 posted:

Use Textmesh, don't use Unity's built in text. Unity bought it from the asset owner because their own solution was so bad. It's now free on the asset store (was one of the biggest sellers on the store) and will eventually be brought into Unity proper eventually.

Oh God, this is so much better. How did I survive without this?



(looks much better at native resolution, but still needs tweaking for readability)

KillHour
Oct 28, 2007


Add a bunch of cheats like old-school games and don't worry about it. People play games to have fun and everyone has fun their own way. If It's offline singleplayer, who cares? People who want The challenge of playing it the "right" way will do so regardless. I hate when a game gets in the way of me having fun with it.

KillHour
Oct 28, 2007


That kind of thing is way easier if you.

1: Approximate your spline to a series of line segments.
2 Map those line segments to points on the spine.

Then you just use the distance along the line segments and linearly interpolate between points on the spline.

KillHour
Oct 28, 2007


Because with events, the thing calling the events doesn't need to know or care about what objects are subscribed to the event. This is important if you're calling a method instead of a function with your event. Let's say you want to implement a method where all units turn on a flashlight at night. Without events, the code making this happen would need a list of references to every instance of your unit class and that list would need to be updated constantly. It gets really messy really fast.

KillHour
Oct 28, 2007


Sure, post the code and we'll break it down.

KillHour
Oct 28, 2007


Boron_the_Moron posted:

It's a very simple program, in which a series of characters are set to a char variable in a class, created by the Main() method. But every time the char variable is set, an event is triggered, which calls the Drop_A method in the main program. Drop_A() checks which character was sent, and if the character was an "A" or "a", it is replaced with an "X". The program then prints the character that was assigned to the char variable (either the original character, or the new X character).

However, now I look at it, I think I see the problem. The thing that was tripping me up was the fact that the Main() method has to create a CharChecker object before it can subscribe an event handler to it. To my mind, that seemed no better than just having the CharChecker do a direct method call to Drop_A(). But having looked at the Unity tutorials, I realise that if the event was declared static, it could be subscribed to whether there was an object associated with it or not. With that realisation, events actually seem stupidly simple now.

Have I got it right?

You understand what the code does, but I think you're still missing why it's powerful, even without static classes.

Let's imagine we're building an RTS with the classic "buildings and units blow up when you lose" feature. How might we program this?

Without events:

code:
using System;
using System.Collections.Generic;

namespace RTS {

	public class GameManager {
		private List<Team> teams = new List<Team>();

		public List<Team> Teams { get => teams; private set => teams = value; }

		void Main() {
			//Dummy for testing
			Teams.Add(new Team("Red"));
			Teams.Add(new Team("Blue"));
			Teams.Add(new Team("Green"));

			foreach(var team in Teams) {
				team.Init();
			}

			Win(Teams.Find(team => team.Name == "Blue"));
		}

		void Win(Team winner) {
			foreach(var team in Teams) {
				if(team != winner) {
					team.Lose();
				} else {
					team.Win();
				}
			}
		}

	}

	public class Team {

		Team(string name) {
			Name = name;
		}

		public readonly string Name;
		private List<Unit> units = new List<Unit>();
		private List<Building> buildings = new List<Building>();

		public List<Unit> Units { get => units; set => units = value; }
		public List<Building> Buildings { get => buildings; set => buildings = value; }

		void Init() {
			//Dummy for testing
			units.Add(new Unit(this));
			units.Add(new Unit(this));
			units.Add(new Unit(this));
			buildings.Add(new Building(this));
			buildings.Add(new Building(this));
		}

		void Lose() {
			foreach(var unit in units) {
				unit.Destroy();
				units.Remove(unit);
			}

			foreach(var building in buildings) {
				building.Destroy();
				buildings.Remove(building);
			}

			//Stuff
		}

		void Win() {
			//Stuff
		}
	}

	public class Building {

		public Building(Team ownTeam) {
			team = ownTeam;
		}

		public readonly Team team;

		void Destroy() {
			//Stuff
		}
	}

	public class Unit {

		public Unit(Team ownTeam) {
			team = ownTeam;
		}

		public readonly Team team;

		void Destroy() {
			//Stuff
		}
	}
}
With Events:

code:
using System;
using System.Collections.Generic;

namespace RTS {

    public delegate void TeamEventHandler(object source, TeamEventArgs e);

    public class TeamEventArgs : EventArgs {

        public TeamEventArgs(Team eventTeam) {
            team = eventTeam;
        }

        public Team team;
    }

    public class GameManager {
        private List<Team> Teams = new List<Team>();

        public event TeamEventHandler teamWon;

        void Main() {


            //Dummy for testing
            Teams.Add(new Team("Red", this));
            Teams.Add(new Team("Blue", this));
            Teams.Add(new Team("Green", this));

            Win(Teams.Find(team => team.Name == "Blue"));
        }

        void Win(Team winner) {
            teamWon(this, new TeamEventArgs(winner));
        }

    }

    public class Team {

        public Team(string name, GameManager game) {
            Name = name;
            game.teamWon += teamWon;

            //Dummy for testing
            new Unit(this, game);
            new Building(this, game);
        }

        public readonly string Name;

        void teamWon(object source, TeamEventArgs e) {
            if(e.team != this) {
                Lose();
            } else {
                Win();
            }
        }

        void Lose() {
            //Stuff
        }

        void Win() {
            //Stuff
        }
    }

    public class Building {

        public Building(Team ownTeam, GameManager game) {
            team = ownTeam;
            game.teamWon += teamWon;
        }

        public readonly Team team;

        void teamWon(object source, TeamEventArgs e) {
            if (e.team != team) {
                Destroy();
            }
        }

        void Destroy() {
            //Stuff
        }
    }

    public class Unit {

        public Unit(Team ownTeam, GameManager game) {
            team = ownTeam;
            game.teamWon += teamWon;
        }

        public readonly Team team;

        void teamWon(object source, TeamEventArgs e) {
            if(e.team != team) {
                Destroy();
            }
        }

    void Destroy() {
            //Stuff
        }
    }
}
(There may be errors, I'm not in front of an IDE right now)

Note how with events, you don't need to keep a list of all the objects you want to do something with - the objects register themselves with the event. Without events, you need to explicitly call every object with loops, which becomes a huge PITA when you have lots of different kinds of things that need to be called.

Edit: Here's a working example:

https://dotnetpad.net/ViewPaste/52RiHWXjwUOF54KPz_4ZgQ

KillHour fucked around with this message at 16:11 on Aug 31, 2017

KillHour
Oct 28, 2007


Boron_the_Moron posted:

I was mostly asking if I understood the syntax correctly, but thank you for the detailed answer. Believe me, I realise exactly how powerful events are. I've got a giant monobehaviour script full of loops, from when I tried to work without them. :v:

I was subtly trying to imply that you shouldn't use static classes that hold data that's going to change (like time of day). I'm guilty of this and I learned the hard way that it's a bad idea.

Boron_the_Moron posted:

One other question, hopefully the last: in C# standalone programs, events require an object and an EventArgs declared as parameters, but in Unity scripts events can be declared with any parameters, even none at all. Why is this? Is it something I need to pay attention to?

Not strictly required, just part of the style guidelines for .NET. Unity technically doesn't use .NET, so it's up to you.

https://msdn.microsoft.com/en-us/library/aa645739(v=vs.71).aspx posted:

Although the C# language allows events to use any delegate type, the .NET Framework has some stricter guidelines on the delegate types that should be used for events. If you intend for your component to be used with the .NET Framework, you probably will want to follow these guidelines.

The .NET Framework guidelines indicate that the delegate type used for an event should take two parameters, an "object source" parameter indicating the source of the event, and an "e" parameter that encapsulates any additional information about the event. The type of the "e" parameter should derive from the EventArgs class. For events that do not use any additional information, the .NET Framework has already defined an appropriate delegate type: EventHandler.

KillHour fucked around with this message at 23:01 on Aug 31, 2017

KillHour
Oct 28, 2007


Ray casts really aren't that resource heavy in and of themselves. You can probably do a hundred per frame no problem.

KillHour
Oct 28, 2007


You can also use rotatearound for the position and set the rotation manually after that.

KillHour
Oct 28, 2007


The point was that you shouldn't be DIRECTLY calling trig functions if you can avoid it. The native transform methods may use trig on the back end, but they're super optimized (I actually don't think they do, though. Unity uses quaternions for all that rotation nonsense).

KillHour
Oct 28, 2007


TerraGoetia posted:

I downloaded Tiled and was able to pop up a map rather quickly:



The output is a JSON file, so my next step is to read that into the game and parse the contents.

I wrote something for this. It's a little basic but I'll throw it somewhere and you can modify it.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

I can't tell if I'm making a video game or a ring menu.

Make the ring menu standalone and customizable and sell it on the asset store.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

A render texture doesn't need a camera at all?

It sounds like it's becoming overkill for just trying to make some damage indicators flash without polling on some timings in an Update() call.

Why don't you use a coroutine? Isn't that what they're for?

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

Disclaimer: I kind of stopped nibbling on the shader when I couldn't figure out how I might even plug it into the text. That being said, I found a _Time property with X-Z components that give the a floating-point time in various scalings. It did compile the shader so I assume I did that much correct.

I'm not so much fixated on not having an Update() as much as I am being able to completely chicken out of having a MonoBehaviour at all. I also am conceding this is kind of unreasonable, but I learn new things every once in awhile when I shoot myself in the foot like this on purpose. So I'm trying to go for it.

Don't you need a monobehaviour to trigger the health bar to go down when you take damage anyways? Just start the coroutine there.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

<whiney voice> :cry: But I wanted to use a shader!!! :cry: </whiney voice>

I mean you could but the monobehaviour would still need to call the shader, probably by changing a variable on it and then changing it back when it's done. Which... would require a coroutine.

KillHour
Oct 28, 2007


So I'm gonna kinda burst your bubble here, but the entire point of shaders is that they're slow but highly scalable. Meaning, doing math isn't as fast as on a processor but you can do that slow thing a whole bunch of times at once for "free". This would be very useful if each pixel had a different value, but when you're running the same equation a thousand times for the same answer, it's just a weird application for a shader. I know this is more of an "I want to do it this way because it's fun" thing, but you're kind of backing yourself in a corner - if you want to adjust the speed or the duration of the flashing you'll have to edit the shader (or pass a parameter in). Also remember that the time won't be offset from the start of the damage, it will be global so all flashing on screen will be in sync. I don't know if that's a positive or not though.

KillHour
Oct 28, 2007


People still use those?

I feel like there were tools to do it back in the day.

KillHour
Oct 28, 2007


Might I suggest writing your game completely in assembly?

KillHour
Oct 28, 2007


Stick all the scripts on a stack and have the magic next button pop the stack with an event.

KillHour
Oct 28, 2007



Man, you are a procedural math monster.

KillHour
Oct 28, 2007


Add a gameobject field to the inventory item and drag the actual item into it via the inspector? I think that still works with a prefab.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

Hmm so Prefabs are technically GameObjects too?

If the prefab is of a game object, yes. They're GameObjects that exist outside of the scene. A few things to note:

- A copy of a prefab can be created in the scene programmatically via the Instantiate method.

- Prefabs will not be included with the built game files unless they are referenced inside an included scene (normally via the method I specified), meaning if you try to instantiate a prefab and it's not referenced anywhere, it will work in the editor but not in a build.

- The exception to this is the Resources folder. Anything inside that folder will automatically be included in the build and can be referenced in the code by some static method I can't remember off the top of my head.

- Editing a prefab directly without bringing it into the scene will permanently change the prefab, even in play mode. This should probably cause an error in the console and doing it in a build may or may not crash the game IDK.

- I don't think that last rule applies to the referenced prefab on the parent since it makes a copy in memory, but it would change any further objects copied from that one so it's probably not a good idea anyways.

KillHour fucked around with this message at 18:54 on Jun 11, 2018

KillHour
Oct 28, 2007


leper khan posted:

To further the point: if you very badly don’t want to incur the overhead of the extra (non-visible) object in memory, you can place the prefab in a Resources directory and do a Resources.Load(path) on it. If you do that, you’ll probably want to abstract it, as dealing with paths everywhere isn’t great.

You're right, it's Resources, not Assets. I fixed it in the OP.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

The problem is more of an aesthetic problem really. I have the player's inventory, which is a list of items and how of them I have. I have a shopkeeper, which has a list of items that can be bought and how much they have (possibly infinite). I might also give the shopkeeper's list a cost. Between buying and selling, I end up putting into/taking from each, and yet I was writing two completely different classes around a list. So it was starting to offend my sensibilities. I might still just go with the generic. The [logic in the] helpers mostly have to deal with [how to manage] items of infinite capacities.
Use a dictionary of tuples.

<string>name : <gameobject>item, <int>price, <float>cost

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

Okay some of the things I have to do that I don't think I've said before:
1. I have 20 of X and I want to remove one. I want to make sure I have 19 left.
2. I have 1 of X and I want to remove one. I want the record gone.
3. When any inventory item changes, I want to fire events.
4. If an inventory has an infinite quantity, I want adding or removing any number of items to have no effect on the number of items.
...

It's a lot of little bullshit like that which I'm replicating across two different classes working on practically identical lists, and I'd rather just have one type that I can still set up using Odin Inspector.

All that stuff is trivial though. And you can use the same dictionary for player inventory as you do for the shopkeeper - the player inventory needs all the same info (what item it is, how many you have, how much it is worth to sell, etc). Just create an inventory component with methods like give(item), take(item), buy(item, cost), sell(item). You can handle all the logic of making sure infinite items never go down and they are removed instead of going negative and whatever else in that component.

KillHour
Oct 28, 2007


Rocko Bonaparte posted:

Okay the structural problem I have (had?) is that the inventory the player had was using GameObjects that were the actual items. What the shopkeeper is using is an inventory containing the prefabs to instantiate. I think what I have to do is make peace with giving the shopkeeper an item like the player or a chest or whatever gets, and give the shopkeeper a new object each time one is taken away. It sounds like I can use Instantiate on instances of GameObjects and not just prefabs, and I can probably just make that the default implementation in the abstract class I use for item logic in case I ever need to do something special. So that might be the stumbling block I have in all this.

Well, prefabs ARE GameObjects, so the shopkeeper has a real copy of it in memory, it's just not part of the scene yet (so it doesn't render or interact with physics or get called as part of the game loop, basically) and it's just one copy, regardless of how many the shopkeeper is supposed to have. I suggest treating the player's inventory the same way - abstracting it until the player actually uses it (Instantiate on equip, basically), then deleting the GO when putting it away. The overhead of instantiating and deleting can be solved by pooling if it becomes a problem. That way you don't have to deal with stuff like "I have to manually disable the renderer on this sword when it's put back in inventory."

I mean, unless the player needs multiple actual different copies of the same object in memory. I can't think of many reasons why you would need that though.

Doing it this way makes implementation of items easier too. Instead of making potions have an OnUse method, they can just do whatever they have to do in the Start method because they get instantiated when they're used. Weapons don't need to all check to see if they're the one equipped, because only equipped weapons are responding to the game loop.

KillHour fucked around with this message at 06:53 on Jun 12, 2018

KillHour
Oct 28, 2007


It could, depending how you implement it. How is your inventory designed? What does it look like? List? Grid? Tetris? Do items stack? Can the same item be in two different slots? Passive in-inventory effects are pretty uncommon outside of weight burden. Do the passive effects stack?

Adbot
ADBOT LOVES YOU

KillHour
Oct 28, 2007


If you're willing to switch to one of the new rendering pipelines, you can use the node based shader editor. It's really good.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply