mattrified


Fighting Games Ideas: Some Ridiculous, Others Not, & Some That Hopefully Never Happen

Fighting games appear to be at a unique crossroad, being pulled in various directions, between their grassroots origins, esports, popular IPs, and more.

I think one of the most important things a genre can do in an attempt to grow is innovate.  Even small tweaks — hopefully in the right direction but not always — can help direct and guide games in the genre to succeed and exceed predecessors.

So, inspired by @goodFGideas on Twitter, here are some ideas — some could be interesting, others purposefully awful.

Also, apology in advance if some of these have already been done; either stated by @goodFGideas already or in an existing game.

Business Models

There isn’t a lot of variation when it comes to fighting games’ business models.  Usually the game is sold at one price; extra characters are then released several months after launch with some cosmetic DLC sprinkled throughout.  There’s also special or deluxe editions that already include the aforementioned character DLC released at launch.  The other variation is free to play, where players get one or two characters to play — that sometimes change on a weekly or monthly basis — and buy the characters they want.

Lately, it seems the post-launch character releases sometimes make games feel incomplete, irritating some players such as the case with BlazBlue Cross Tag Battle’s DLC character packs.  I sort of understand the logic behind the developer’s choice.  If you launch the game at the full $69.99 price, it may appear too high and scare some more fiscally responsible players away.  This can also make supporting the game post launch with server support, balance updates, and patches more difficult.  Doing DLC character packs allow some players to buy at the lower price, and if they decide they don’t like the game, move on.  Some will enjoy the game and continue to support it by buying the DLC; others will wait until the game and/or DLC is cheaper and buy it all at once.

Anyway, here are some business models future fighting games could try (or not):

Buy Characters?  Why Not Buy Modes Too!

I think Dead or Alive 5 already did this to a degree; allowing players to buy the story mode for an additional cost in the Final Round edition of the game, but why stop at story mode?  Are you a casual who only wants to play story mode and arcade mode while you wait for a 100+ hour rpg to come out?  Then just buy Story Mode and Arcade mode, just $5 each!  Are you a pro that doesn’t care about narrative at all and just want to grind, lab, and play online?  Then just get those!  Similar to ordering a’la carte at a restaurant, you buy just the modes you want instead of a large meal with a side you’ll probably forget at the table anyway despite getting a to go bag.

Regardless, this idea focuses on the compartmentalization of fighting games.  Devs release the game at a relatively low price and see what modes are selling, need expansion, etc.  Is nobody playing the single-player content?  Then don’t make more of it!  A similar logic could be applied to cosmetic character DLC.  Why make more costumes for character X if they are the least used character?  The biggest risk is the game may appear unfinished, so upon release, a good number of these modes would need to be playable.  Not, “Oh, 6 months from now, you can buy Story Mode.”  It should probably be a day one release that’ll be expanded upon if successful.

Training Mode by the Hour?!

For professional players, training mode is probably one of the most important modes in a fighter outside of versus and online play.  And training modes have definitely evolved from earlier fighting games.  You can record inputs to be played back or program an AI to react in a variety of ways.  Though most training mode features were probably used by the developers to help debug key game features, there is still a considerable amount of UI work involved in getting these features usable to players.

Now, athletes train in gyms and on fields every day.  The use of that equipment and those facilities isn’t free, so why should training mode?!  So, similar to Tekken Revolution’s quarter system, why not use a similar system for Training Mode?  If a game could make even $0.25 for every hour players are in training mode, can you imagine the revenue?!  Heck, even just having ads pop-up could probably create decent revenue steam.

Now, will players be annoyed by this?  Yes.  Don’t do this.  This is a horrible idea that I hope a company, no matter how hungry for profit, never does.  Though I liked Tekken Revolution’s quarter system to a degree  — free to play with quarters you kept by winning or gained back every hour — doing this for training mode would probably cause lasting damage to any IP that tries it, except Street Fighter or Tekken; these two brands could maybe survive the backlash from hourly training mode fees.

Player Cards & Paid Modifications

Player cards are pretty common in fighters.  They show your gamertag, wins, losses, achievement-based banners, and more.  My idea:  Let players modify them more freely.  If you don’t wanna show how many losses you have, don’t.  Want to keep your number of wins a secret?!  Remove it.  Want multiple labels?!  Go right ahead!  Obviously, there’d be a limit.  I imagine you’d have to fit items on the card similar to Resident Evil 4’s inventory system.

So the business model portion?  Well, most banners and labels are earned through playing the game — beat arcade mode with this character on hard, win 100 ranked matches, etc.  Why not just let people buy these?  They don’t need to be expensive.  If you like a label, just buy it.  “But they didn’t EARN it,” you may decry.  Who cares? (Obviously the person asking, but I digress.)  A player earning a banner by doing something like beating arcade mode on Expert is good for them, but getting a dollar because the player didn’t want to do is good for the developer (but could also speak to a poorly designed arcade mode or AI.)

Also it could create an interesting headgame during online matches.  “Wait, they have the banner for doing all of that character’s challenge combos?!  They must be really good with them!” Are they, or did they buy that label?  Well, you’ll quickly learn once they drop a combo or two, but you were a little nervous, right?  No?  Fine.

Furthermore, wanna clear the number of losses you have?  You can pay to have your criminal record expunged — or at least a Google search tells me that — why not you loss record?  Or even your win record!

You may ask, couldn’t this create an atmosphere of distrust?  Well, I mean, so does rage quitting, a common practice players do to preserve win streaks, even despite punishments companies implement.  And I’m pretty sure there are people who can easily figure out to mod their records somehow.

This idea is probably also very bad.  I am curious to how many players actually even care about their player card or how many would pay to expunge their losses or to get a special, difficult-to-earn-badge.  Does it cheapen said labels because some players didn’t really earn it?  Yes, but at the same time, when you earn one for real, knowing you earned it and saved money in doing so, that’s a good feeling too.  Overall, I think that’s what the idea is referring to; more concepts that you can either earn through play or just buy.  I believe Injustice 2 allowed this with Brainiac.  You could either buy him immediately — useful to tournament organizers for sure — or save a few bucks — at the cost of a few hours — and earn him by completing Story Mode.

Single Player Content

Single player content is a surprisingly contentious topic in fighting game.  To the main design of a fighting game, single player content isn’t really important.  If the actual gameplay of a fighter is boring or uninspired, having good story, arcade, or campaigns modes really aren’t going to save it.

SPC seems to really excel as expanding the brand of the game and its lore, letting players know why should they invest in this world or these characters.  Also, it can be the vehicle to crossover into merchandising, expanding the brand into other media — comics, movies, shows, etc. (Or do the opposite, get more people interested in an existing brand.)

Stop Copying NetherRealm Studios

I’ll admit, I really liked Mortal Kombat 9’s story mode.  It did a great job at exploring the rather complex (or convoluted) lore of the Mortal Kombat series and allowed players to try multiple characters, acting almost like mini introductions to the cast — or kast?  Sorry.

Though I feel after that game’s success EVERY AAA fighting game has copied that formula — do a fight, watch a cutscene, do a fight, repeat.  I can’t imagine how expensive these modes are.  With all of their animation, narrative design and writing, voice acting, and various one-off systems needed to implement just to — and I could be wrong about this — have a majority of players ignore or ridicule it.

One issue is often these fights usually aren’t anything special or unique from the basic play experience.  Maybe you’ll face someone like Peter the Cop from Street Fighter V or an unplayable boss, but they are just small snippets of unique play.  Also, cutscenes seems to get longer and longer, highlighting an already problematic flow style.  At least Guilty Gear Xrd just did away with the flow problem and made a movie with no fights.

Anyway, the “idea” is just to try something new with this.  Shorter cutscenes, more tutorialized or unique challenges, just something that’ll separate it from NRS’s current story mode implementations.  Something that’ll make players go “Hey, playing this story mode was a lot more beneficial to me than just watching it on YouTube.”

Invisible (or Hard to See) Tutorials

“Tutorial” feels like a dirty word in game development.  It feels like a lot of developers and designers — myself included — seem to dread creating them, especially if they are leaning towards being very text heavy or requiring a lot of unique features that are only for the tutorial.  A lot of this stems from the feeling that players often ignore or rush through them, missing key points and then quitting out of frustration because they ignored said points.  I think the best tutorials are once considered to be invisible; however, implementing these ideas in fighting games seems rather difficult since the progression can’t be neatly laid out like in Half Life 2.

Tekken 7’s Story Mode is an example of a single player content that tries to replace a tutorial — though I think doesn’t do a great job of it, particularly due to the limited character set, easy input mode, and other factors.  Regardless, I think, if a developer isn’t going to take the time to do an in-depth tutorial, some level of single player content to teach various aspects of the game is important.  Challenges such as blocking a series of attacks or fighting opponents that only take damage from juggles I think can help a player learn, but also explore different aspects of the game without feeling like they are necessarily doing a tutorial.

Online Ideas

I feel one thing that hasn’t changed a ton over the course of fighting games has been online mode.  You have ranked, casual, and lobbies to play with friends.  Besides things like Factions in Mortal Kombat X, I’m unsure what innovation is really being brought to online play.

Mentorship

Now this idea probably already doable to a degree, but the problem it’s trying to solve is that new players often jump right online and just get demolished by more experienced players.  Those players pretty much have two options then:  try to learn and improve or just leave and move on to a different game.

What if, instead of immediately jumping online, players could learn from a mentor of sorts.  Apparently online training modes already exists, but this service would more or less allow more experience players — probably determined by rank or experience — to train and mentor newer players through private one on one online training.

This probably already exists through more experienced players Patreons — donate X per month and I’ll play with you for an hour a week or something — but what is the game could facilitate this more?  You can then rank which mentors were helpful or unhelpful, etc., and said mentors are rewarded.

Again, probably not a necessary idea as the community and players could facilitate this instead of the game; however, I think by putting it out there, it could help newer players realize that the fighters take time to learn and sometimes they just need help from some more experienced in the genre to succeed.  Also, it could be a nice alternative to videos, which don’t always work for individuals who learn more kinesthetically.  I mean, most people don’t learn martial arts through videos or books, they learn through classes with a skilled teacher, and I think fighting games taking a similar approach could be helpful in growing a lasting playerbase.  This idea also fails if the player refuses to believe they need help of any kind as finding a learning from a mentor is usually an admittance that one needs help — which shouldn’t and isn’t something to be ashamed of, yet it often is believed to be one.

Nemeses

When online, sometimes you lose, you lose bad, and you want that over seasoned rematch.  Most games do keep track of your last couple of opponents.  So you could probably find that gamertag, invite to a match, and have your revenge match, but what if the game did more?

Inspired by the Nemesis System of Shadow of Mordor / War, what if fighters had a similar system?  Every time you lose a match, you have the option to make that opponent a nemesis.  Then, in the future, you can rechallenge that player.  It probably wouldn’t go toward your in-game ranking, but you could have built-in rewards to create an incentive to challenge nemeses but also for said nemeses to accept rematches.  Though not a huge or new concept, it’s a small tweak that could make online play more interesting and motivate players in a different way, giving them a reason to come back in the future.

Asymmetrical Effects

Some fighters have special status effects such as turning a character invisible or poisoning an opponent.  I think it would be interesting that, when playing online, the effects were visually asymmetrical.  If I turn invisible, I can still see my character locally — probably with some fancy effect so I can tell the effect is still active — but on the opponent’s side, my character is completely invisible.  In the case of poisoning, the entire screen gets a slight purple hue, not only to indicate that the negative effect is active but to give an additional layer of difficult by affect the game’s visual.  This is a short idea, but I feel something small like this could make online play more interesting.

Answer “Why?”

I feel the biggest issue with fighting game online modes today is that they don’t really have strong incentives to keep playing or can feel like a grind.  They don’t give a great sense of progress nor do they really offer a reason to keep coming back.

You get the trophies, now what?  They are usually one-and-done.  You get top rank — and if you do, you’re probably already a pro player and it doesn’t matter because you’re preparing for some tournament — but in that case, tournaments already provide the why.

But what drives average or below average players to keep coming back?  They may enjoy playing the game itself but just aren’t that great, losing online becomes tedious, and they may not have the time or resources dedicated to improving quickly.  Should they quit playing?  Developers never really want the answer to that to be “yes.”

So developers need to contemplate, besides the frustration brought on from losing, what are they getting out of it?  A lose streak and loss record already is a harsh punishment to some — something else I’ll applaud Tekken Revolution for doing away with.  If said player does feel like they are making some progress on their play and eventually will break through and end the losing streak, nothing really needs to be done; however, there will always be players that when faced with even minor frustration, feelings of regression with no accomplishment, will probably quit and move onto something else.  This is a reality some games need to face; developers can’t please everyone, but the more they can prevent players leaving and quitting, the better.

Overall, I guess the idea is give players a reason to play online, regardless of the outcome.  Sure, the subjective nature of the game being fun is there — if you can make players enjoy losing as much as winning, you’ve made great progress, but I feel like even a minor reward for losing, can help.

Some games have made efforts towards this, awarding small amounts of EXP or in game currency just for playing.  Some games offer daily challenges:  throw an opponent five times during a ranked match, win 3 matches with X character — though personally I don’t like these because they force you to change your play style to complete said challenges, ultimately serving as a distraction.  And developers can’t do what some mobile or single player games do — allow players to buy in-game currency so they can overcome the next challenge more easily.  Maybe this isn’t really a problem, but just a reality of competitive games.  You have your core players that will utilize online play the most, and more casual players who will consume single player content and move on as they make little to no progress online.

I guess this isn’t so much an idea — and it’s not really unique to fighting games.  This is more or less just expressing the opinion that online play currently can feel like a pointless grind, that some players need a better reason to come back to online mode, a “Why?”  I think the game design lens of visible progress has some good questions for this; I feel showing players their progress could help answer this too, but of course, the counter question, do players even care about their progress if said progress isn’t just win accumulation?

Conclusion

In conclusion, here is a list of ideas that I could have probably tweeted instead of writing this:

  • Allow players to buy what modes they want to play
  • Charge a nominal fee per hour of Training Mode usage
  • Allow players to buy any badge for their player card as well as the ability to expunge their loss record
  • Stop copying NetherRealm Studios Story Modes
  • Make more single player content fights unique and/or act as invisible tutorials
  • Create an online mentorship program to allow more skilled players train less skilled ones
  • A nemesis system that allows players to quickly have revenge matches and rewards both players for victory and participation
  • Asymmetrical networking effects between players.
  • Give players a reason to play online through visible progress and/or minimal reward/punishment.

Oh and one more I didn’t feel like writing about at length — use machine learning to prevent infinites and improve balance.  I didn’t write at length on this, because I feel like machine learning is a buzzword I don’t know a ton about.  Regardless, I am curious if someone could create an AI that could take a game’s data, analyze it, and point out a lot of the initial flaws.  It probably wouldn’t find all of them, and worse, it could result in a sterile experience so it could end up being a horrible, expensive idea.

Again, some ideas are maybe okay and could extend to other genres; some ideas are awful, and I would secretly love to watch attempts at subduing the PR nightmare manifested by someone trying them.

Overall, I think the genre is in an interesting period.  I think things are exciting and great, but I also feel they could be improved and evolve, and I hope we continue to see developers attempt to evolve the genre through even the slightest changes in the future.


It’s 2018!

It’s officially 2018!  With a new year comes new hopes and dreams and ambitions — 5% of which will probably get accomplished or worked towards.  Despite this, I think it’s still important to establish these goals.  Anyway, this post will go over how 2017’s game development went and things I’d like to shoot for in this new calendar year.

Battle High 2 A+

I started 2017 by showing Battle High 2 A+ at MagFest.  It was an exciting trip, although a little stressful.  I really enjoyed showing the game off to potential new players; in fact, I did again at the Pittsburgh Retro Gaming Convention in March followed by a small tournament at ReplayFX in the summer.

Besides showing the game off, I also wanted to try and get a new character in the game; however, I got distracted by TrueSync, which I’ve mentioned in previous posts and will discuss later in this one.  The big issue is that adding new character isn’t cheap, especially this one as all of the sprites aren’t finished.  I could try and release the character as paid DLC to make up for some of the cost; however, that involves extra work — integration, VO, new music, etc.  I’m not going to say that the new character won’t come in 2018; however, it’s definitely lower on my list of priorities.

In 2017, however, Stream Greenlight was replaced by Steam Direct, allowing indies to publish their game onto Steam for a small fee, so I spent a small part of November and December releasing the game on Steam, so though I didn’t get a new character into the game, I did get it released on a new platform to hopefully expose the game to more players.

I’m not sure what my goals for Battle High 2 A+ will be in 2018.  I’d love to get the new character done, update the 3D backgrounds, or get netplay working; however, these are all currently low priority to me.  I’m super grateful for the fans of the game, and I know I could do a lot more to make the community grow; however, I want to move onto new ideas, a lot of why the TrueSync stuff was such a distraction though a very good one.  I’m not going to say the game is dead and that I’m never going to work on it, but I’m not going to promise tons of new content or changes either.  I think the most I will do is work to make sure all versions — itch.io, Game Jolt, Steam, and Xbox One and all consistent and up to date.

Project MerFight

Project MerFight was a rough part of 2017.  For awhile, I wanted to work on a tactics RPG with fighting game elements.  I learned a lot about art, pathfinding, and more; however, as I worked on the project, I just felt overwhelmed, that it was too big for me to take on by myself, that I was focusing on areas that just don’t really interest me that much.

With that, I made the difficult decisions, despite months of work, to put the game on hold indefinitely.  I think what this helped me realize is that I should really only focus on games and aspects of them I’m really passionate about, until I’m able to — if ever — build a team to help me in these others areas.  As much as I enjoyed Namco X Capcom, which was serving as a lot of my inspiration, there were features I just wasn’t excited to implement or felt overwhelmed trying to.

Though it feels like a failure, I think what I learned will ultimately help me and future ideas improve.  This if, of course, if the information is well documented, something I also want to improve in 2018.

TrueSync

In a previous post, I mentioned TrueSync, a online rollback framework for Unity3D by Exit Games, the company behind Photon.  Although not perfect — it’s still in beta — I think it has great potential to democratize deterministic online gameplay for a lot of developers that, like myself, find it rather difficult to approach or are unsure where to start.

I spent a lot of time working and experimenting with it.  I made a prototype fighting game using it called ProtoFighter; however, I also want to learn and improve my art and animation skills instead of a majority of my assets being from the asset store.  I’d really love to get my own fighting game framework fleshed out so that I can create new games using TrueSync more quickly.  This will take some time; for example, I refactored a lot of ProtoFighter so I could make it more extensible with other types of games, and there is still a lot of refactoring work to do.

2018 Goals

So 2017 doesn’t sound too exciting.  I focused on showing off Battle High 2 A+ and learning TrueSync and chose to “pause” a game idea; however, I did a lot of other things too, but they are all small prototypes, difficult to organize in any meaningful way.  Battle High, Project Merfight, and TrueSync are probably the most important when it came to my independent game development.

Anyway, here are some goals I’d like to aim for (not necessarily hit) for 2018:

  • Develop my fighting game framework and create a prototype with TrueSync
  • Show off a new game, prototype, or Battle High at another show, maybe one not based in Pittsburgh
  • Submit a new game idea (not a finished game) to Xbox One for the ID@Xbox program
  • Better self-care — sleep & exercise more, eat healthier, etc. (really, these are life goals but a new year always reminds me that I should be doing them as I get older)
  • Better my visual art skills — shaders, Substance, animation, etc.
  • Write more posts and/or tutorials as well as try dev streams.
  • Submit a talk to a conference — and do it NOT on the last day for submissions

This isn’t the most exciting list, and I know I won’t hit all of these.  Every new year, however, reminds me to step back and really observe and update and pivot on what I’m focusing on.  Since one of my goals is to write more posts here, hopefully I’ll write about my progress soon!


ProtoFighter Dev Blog 01 4

So for the past couple of months I’ve been working on a new fighting game prototype.  After discovering TrueSync by Exit Games, I’ve been trying very hard to create a new fighting game with it.  Again, one of my biggest regrets with Battle High is that I was never able to implement multiplayer before its release.  I definitely feel that TrueSync could definitely help me achieve that!  Anyway, I decided to write a little bit about the game and what I’m trying to do with it.

ProtoFighter

I chose this name because what I made was a prototype, and I wanted to make this clear.  I decided to use only assets from the Unity3D Asset Store, which TrueSync already is.  This includes my characters, audio, and more!  Here is a short list of some of the assets I am using:

Goals

I had several goals while making this prototype.

Learn TrueSync With a Focus on a Fighting Game

My first goal was to learn TrueSync and make a game using it.  I think I accomplished this.  In fact, it’s not my first TrueSync experiment.  Diamonds Not Donuts, a small game I released on itch.io for free, is!  That being said, for ProtoFighter, I wanted to focus more on fighting games and various issues concerning them.  ProtoFighter has a lot of gameplay functionality that most fighters do — blocking, jumping, attacking, special moves, supers, rounds, etc.  Obviously it’s missing a lot to be a complete fighting game package — single player modes, balance is a MESS, more characters, etc.  Again, for a pre-pre-pre alpha, I think I achieved my goal, but of course, when it comes to TrueSync, there are still a ton of questions I have and hope to continue to answer them as I expand upon this prototype.

Make a Fighter That Is Slightly More Accessible Than Most

Though not TrueSync related, I’ve always wanted to try and make a fighting game that was a bit more accessible to the average player.  Maybe not as extreme as Fantasy Strike, but something that I could still explain relatively easily.

In ProtoFighter, though I sadly haven’t released a tutorial yet, I tried to do this.  Essentially, instead of performing quarter circle attacks, I simplify this to forward or back plus an attack.  Now, a lot of people would immediately say this oversimplification could cause issues such as instant dragon punches or anti-airs, so to solve this I did two things.  Firstly, all initial moves such as forward+punch have rather long start-up and are reserved for moves like overheads or projectiles.  Then, every special move has a “secondary” special that branches from it.  For example, forward+punch may begin an overhead but then pressing up before the attack activates, a secondary attack, probably an anti-air attack, would be performed.  The hope is that performing the initial move and then the secondary move will require just enough time and frames that the anti-air move won’t be so instantaneous.  Maybe this won’t help, but the idea it’s simple to actually perform an attack, but requires some dexterity and memorization to cancel one move into another properly.

A secondary idea I then had is to still allow players to perform attacks using quarter-circles; however, these players would be rewarded with a slight meter bonus, so you don’t have to perform moves properly to compete or play, but players who can are rewarded slightly for taking the time and effort to perform more complex inputs.  I can’t really tell if this input system will be good or not until someone tests it, which is why I released the prototype.

Create a Framework

My third goal was to begin creating a framework so that I can create future titles, TrueSync or not, more quickly.  A lot of games I work on are usually fighting game influenced, so I wanted to construct a framework so that creating future titles, whether 2D or 3D, would be easier in the future.  Though not perfect, I definitely tried to abstract more of my classes and functionality and believe I could quickly go from this 2.5D fighting game to a 3D game rather quickly with few changes.

TrueSync Tips

So, for this fighting game, I learned a good amount about TrueSync.  TrueSync attempts to be deterministic, allowing a local player’s inputs to be immediately respected, passed over the network, and compared to the game’s state and rolled back if there are inconsistencies found and resimulated.

The issue though is that Unity3D wasn’t built to be deterministic.  Its use of floats and random system for example can cause various issues.  It’s animation system also isn’t deterministic so trying to perfectly simulate results across two machines can be rather problematic.  Anyway, here are some tips I found were helpful for completing my prototype.

Note, these tips were written for Unity3D version 2017.1.1f1 and TrueSync version 1.1.0B.

Don’t “Press” Inputs

TrueSync uses a unique method to capture and send input, it’s called OnSyncedInput.  Here’s an example of how it works.

public class MyTSClass : TrueSyncBehaviour
{
    public override void OnSyncedInput()
    {
        TrueSyncInput.SetBool(0, Input.GetKeyDown(KeyCode.Space));
    }
}

So in the above, TrueSyncInput is used to pass inputs over the network.  The first argument is a byte, used as a key.  I’m just using 0 for now, but if you use multiple,  you should probably assign them to a constant.  Then, I’m using Input.GetKeyDown to send a bool if space is down or not.  One issue with this method is that it is performed similarly to OnFixedUpdate so calls such as “Input.GetKeyDown” don’t work consistently as when OnSyncedInput is called, Input.GetKeyDown is sometimes missed.  To resolve this for button inputs, here’s what I did:

public class MyTSClass : TrueSyncBehaviour
{
    bool hasPressed = false;

    public override void OnSyncedInput()
    {
        bool singlePress = false;
        if (Input.GetKey(KeyCode.Space))
        {
            if (!hasPressed)
            {
                hasPressed = true;
                singlePress = true;
            }
        }
        else if (hasPressed)
        {
            hasPressed = false;
        }

        TrueSyncInput.SetBool(0, singlePress);
    }
}

This change uses a bool that is set to true when OnSyncedInput is executed if the space bar is currently down. The toggle is then reset once the spacebar is no longer being held down.  The bool that is actually pased in TrueSyncInput.SetBool is only set if the keyboard is down AND hasPressed was false before being set to true.  This way, the first entry of TrueSyncInput will be true for only one execution of OnSyncedInput.  This should prevent any issues with OnSyncedInput missing an input as the average button press usually occurs for a few frames.  I don’t use this method exactly in ProtoFighter, but the idea is similar.  Instead of doing separate Booleans for each input type — up, down, left, right, etc. — I use an integer and bitmasking to change it during OnSyncedInput.

Treat TrueSync Like A Separate Engine

This sounds silly as Unity3D is a game engine; however, to make TrueSync’s determinism work properly, you have to use a lot of unique structs and classes that it introduces.  There’s FP, or FixedPoint, for float values for example and TSVector for Vector3’s.  Also, TrueSync has its own Transform class (TSTransform) that does not have all the functionality — at least now — that Unity3D’s Transform class has.  You can’t use children the same way and certain methods such as those that convert transform information from world to local space are missing.  Overall, you can’t just take a finished game and integrate TrueSync into it quickly.

One trick I had to do, for example, was figure out a way to align character hit spheres to certain joints.  In a normal setting, I could just use the following:

Animator anim = GetComponent<Animator>();
Transform t = anim.GetBoneTransform(HumanBodyBones.Chest);
Vector3 chestPos = t.position;

However, one problem is that this creates a Vector3 and even though I can convert the position to TrueSync’s Vector3 equivalent, a TSVector, they may be different values between the multiple players due to floating point precision errors.

To resolve this, I built a tool to cycle through my animations and store important point information as a TSVector  in a ScriptableObject.  I don’t save the position though; instead, I save the vector from the center to this point.  So, to get where the chest would be in my animation, it would be something like the following:

TSVector localChestVector = GetChestPosition(frame);
TSVector worldChestVector = tsTransform.position + tsTransform.rotation * localChestVector;

So, in the above, I’ve gotten a local vector for my chest position and then used the position of my player and rotation to define the world position for my chest now.  You’ll also notice that I’ve used a frame.  This is because a lot of fighting game interpret things as frames, and I believe interpreting your deterministic game in TrueSync is a lot easier to understand through the concept of frames than through time.  Even though my 3D animation is made up of curves, I store different bone information in these TSVectors so they can be referred to later regardless of the rotation or position of my character.  I also do a similar technique for moving a character by their root animation without actually having the Animator drive it.

No Animators — At Least How You Think

As of right now, TrueSync doesn’t have an Animator class.  For fighting games, this can be an issue since animations and the accuracy of said animations is so important.  To handle this, I did the following:

  • Stored all of my animator data in a separate data structure, mostly just my transition parameters and conditions
  • Muted ALL of my animation transitions
  • Disabled the Animator Component
  • Use Animator.Update(float)

So, even though the animator is disabled, Animator.Update(float) still allows the Animator to be updated.  Even though you do have to use a float instead of an FP, the amount I update is determined by the frame I’m supposed to be on, so my update function looks like this.

FP syncedFrame;
FP localFrame;
Animator anim;

private void Update()
{
    anim.Update(((TrueSyncManager.DeltaTime) * syncedFrame - localFrame).AsFloat());
    localFrame = syncedFrame;
}

So, here I have syncedFrame which is the frame of my animation that is set during OnSyncedUpdate.  Then I substract the syncedFrame to the localFrame and convert it to a float value.  I then set the localFrame to the syncedFrame.  I used FP instead of integers in case I want to play the game in slow motion.  This still needs some tweaking, however, but it gets the general idea across.

Overall, using Animator.Update(float) is great because it allows me to still get a lot of the functionality of Animators

  • Transition blending
  • IK
  • Mirroring
  • Humanoid rigs

But with more control.  This is one reason all transitions in the Animator are muted actually.  Because I don’t want transitions to happen automatically and switch states suddenly if there is rollback.  Doing it more manually allows me to switch state when I need to.

Just one small part of my AnimatorController; the red arrows show that my transitions are muted.

Anyway, the future of ProtoFighter is uncertain.  I will most certainly not release this as a full game, but instead a fighting game demo.  I know in this current 2D version I’d like to do the following:

  • Add rooms and lobbies instead of the “Ranked Match” system it uses now
  • Add stage select
  • Add an interactive tutorial
  • Balance and clean up the existing characters, Protolightning and Protaqua
  • Start looking into AI and single player modes

Overall, the goal with this game is to eventually get a framework to a place where I can experiment with a variety of gameplay styles and making something myself later down the road, hopefully sooner rather than later.  Maybe I can even use this to integrate TrueSync into Battle High 2 A+ — though I make zero promises.

ProtoFighter is available on itch.io & Game Jolt for free!  If you download the game and play them, I’d love to hear your feedback — but make sure you try the multiplayer as that’s the main area I’m trying to focus on.  Also, if you have any questions on TrueSync, I’d love to try and help as I think it’s a great asset and can help give online functionality to a lot of new indie game content — fighting games and other — in the future.


Unite 2017

I recently returned from Austin, Texas from Unite 2017 one of several conferences Unity holds annually to discuss upcoming features about the Unity3D game engine.

I usually write long posts about my experience at these conferences, but this year was a bit, not a letdown per se, but I just didn’t feel I got as much out of it as I have in previous years.  I didn’t leave feeling inspired and invigorated.

First, there weren’t a ton of sessions like in previous years, in fact, the first day of the conference, only the expo hall was open.  It was a nice expo, but also felt lacking in ways.  Last year Unite was held in a difference convention center, so it’s possible that the larger expo floor made it feel smaller, but regardless, having no sessions the first day just felt odd and had myself and others question “What’s the point?”

Overall, none of my sessions blew me away nor did the keynote.  Most of them were great overviews.  There was a talk about different network architectures from Exit Games that I liked as well as one that went over the character building techniques of the Rick & Morty VR game.  There was also a decent discussion and demonstration about future AR features coming to Unity3D in the coming years.

I think that would be my next biggest complaint.  A LOT of AR and VR, almost too much.  I understand they are exciting technologies, but I wish, like last year, there were a few more talks about design itself or just more variety in general.  I always feel that no matter how good your engine or tools are, if your games aren’t designed well, it won’t matter.  Maybe there were, and I just missed them.

Overall, I think it was worth the price of admission but am definitely on the fence if I’ll go next year or go to a different conference such as GDC instead.


Maxscript: Constrain to Biped 2

So in 2016, I wrote a maxscript to constrain humanoid characters to 3ds Max bipeds for easier animation.  Every once and awhile, I get someone asking me for the script and some questions regarding it, so I decided to write a post about it for the future.

Here are two videos demonstrating how it works and its setup:


A quick summary:

  • What this script does:
    • Builds a biped, sizing it to fit a specified character
    • Uses Orientation Constraints to drive the original rig’s bones to the biped
  • What this script does NOT do:
    • Require new reskinning or transfer of the original rig’s skinning, which often causes issues.
    • Transfer .fbx animations — or any kinds for that matter — to the biped.  This is JUST for the rig itself.

Why?

Why write a script like this?  Well, for one I’m old-fashioned.  I’ve always liked the 3ds Max’s biped.  It’s not perfect, a little buggy; however, I’ve felt it gets the job done and has a lot of extra features — saving poses, postures, animations, etc. — that writing on my own would be rather time-consuming.  Additionally, exporting just the biped itself can be rather problematic as it sometimes moves bone objects, which causes issues with animation retargeting since that is focused more on rotation.  Since this original rig is preserved and only driven by Orientation Constraints from the biped, this is less of a problem.

Then, despite the fact other character creator tools such as Mixamo supplied rig to biped scripts, though scripts never quite worked as well as I would want, often deforming the original mesh or rig and causing unforeseen issues.

The Script

Firstly, you can download the script here.  Note, this script was written for 3ds Max 2016 but has also been tested in 2017.

Instructions

Unzip the downloaded file and run the .ms file.  You should then see the following window:

There are two columns.  The left column, Biped Bones,  is for all of the biped bones that’ll be created; the right column, Character Bones, is for the bones in the original rig.  Note, there are 2 neck bones and 3 spines in the left column; however, the 2nd neck joint and 3rd spine joint do not need to be defined and are prefaced with [IGNORE].  When this was written, it was for one specific rig that used 3 spine joints and 2 neck joints; however, since this caused issues with Unity, I decided to remove those; thus enabling it to work on more humanoid rigs.  Unity can now handle a 3rd spinal joint, but still doesn’t use a 2nd neck joint in its default, humanoid rigs.

To start populating the right column, click the row you want to define and then click the bone / node you’d like to associate with the biped bone.  It’s a bit tedious.  There are two buttons for saving and loading, Save Selection Set and Load Selection Set, respectively, that can help a bit.  If you know the names or they are named in a way that can be populated quickly through copy-and-paste, this can be done by saving a text file, updating it, and then reloading it.  In the .zip, there are two examples of these files; they are setup for use with iClone Character Creator 1 rigs.

Once the right column has been populated properly, the Validate Bones button will check to make sure the bone slots are all assigned.  This will also show a pop-up for any bones that are missing.  Warning:  This’ll generate a pop-up for every missing bone.  

If all bones have been signed, click the Build Biped button.  This will generate a biped that’ll match the size of the original rig.  You do not need to, but it is suggested to then rotate the biped as closely to the original rig.

Then, the Build Helper Rig button will create a new rig that is identical to the original rig except it’s bone orientation will match the biped’s, meaning the up, forward, and right axes will match the biped’s.  This is important for the next step.  Essentially, an early thought for this experiment was to:

  • Build a biped
  • Align the original rig to the biped

However, one of the big issues is that rigs and their bone rotations can come in a variety of orientations.  If you use 3ds Max’s default align too, arms will sometimes be rotated in strange positions.  The helper rig solves this by standing as the middleman between your original rig and the biped.  It’ll be the same size as your original rig but the bone’s will match the orientation of the biped.

Next there is the Align To Biped button.  This aligns the helper rig to the biped and then the original rig to the helper.  This is why aligning the biped to the original rig helps; otherwise the changes can look rather broken.  They are easy to fix because, again, this is just affecting rotation and not placement of the original rig.

The Create Constraint button is the final step.  All other steps should be completed first — including making backups in case there is an issue.  This will create Orientation Constraints between your original rig to the helper rig and from the helper rig to the biped.

Once this is done, the rig should now be driven by the biped.

Other Buttons & Tips

As you may note, there are two buttons I’ve yet to discuss, Quick Parent and Quick Child.  Quick Parent create a parent bone the selected bone’s parent and itself.  This would be used for something like a rig with only one spine.  This will create the second spine automatically that can be used in the rig.  Then, Quick Child, creates a joint at the end of a joint.  The biped rig requires 5 fingers as well say finger nubs, for example, and this button will create these quickly.

Another tip is that if you create a child, for something like the head nub, make sure that they are aligned perfectly vertically; otherwise, the head will be tilted when aligned to the biped.  The toes have a similar problem I haven’t quiet figured out, but again, aligning the created biped as closely to the original rig as possible will help resolve some misalignment issues.  Another tip is that instead of rotating the biped once it’s created, rotated the bones of the original rig to match the newly created biped as closely as possible.

Final Steps

After completing the steps, you can now animate just the biped as your would except you should NOT rotate the pelvis bone; this causes the hip and spine bones to translate slightly, causing issues upon export.  They will export fine, but your animations won’t match perfectly and when importing to Unity, you’ll get errors about how those bones have translation data and that said data will be ignored if it’s part of a humanoid avatar.

Also, don’t export everything; use the export selection and select only the original rig’s joints and/or any meshes you’d like to export.

Quick Summary

  • Unzip this file.
  • Run the BipedRigCreator.ms script in 3ds Max
  • Define the joints in the right column, creating children or parents where needed
  • Validate the bones
  • Build the biped
  • Build the helper rig
  • Align to the biped
  • BACKUP (if not already)
  • Create constraints

Wishlist

I’m unsure if I’ll add anything to this script anytime soon, but here is a list of things I’d like to do:

  • Streamline the bone selection or remove the left, right column idea as they aren’t lined up
  • Allow for multiple spine joints / make the correct number of spines based on the number of spine joints assigned)
  • Adjust errors for the head nub and foot nub issues
  • Not show a pop-up for every missing bone, but instead a list of all missing bones upon validation

Anyway, if you use the script, great!  I’d love to see what people do with it.  Again, I mostly wrote this so people who would like to use it in the future have something to refer to.


Unity3D Script: Quick Texture Editor

Last year I wrote a Unity3D editor script for combining textures as well as swapping and combining their different color channels.


Someone on YouTube recently commented, asking for more details. Since I haven’t touched the script in over a year, I decided to just make the script public. It’s not perfect and some of my comments don’t make sense. I’ll probably clean it up in the future, or at least add better documentation.  I sound very professional right now.

via GIPHY

What this script does:

  • Allows you to swap color channels
    • For example, take the red channel of a grayscale smoothness map and apply it to the alpha channel of your albedo texture
  • Allows you to combine texture onto a new, larger texture
    • You have two 512×512 texture and want to combine them onto one 1024×512 texture

What this script does NOT do:

  • Resize textures
  • Rearrange meshes’ UVs
  • Paint onto textures
  • Create textures other than PNGs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.IO;
 
namespace MattrifiedGames.Assets.TextureHelpers.Editor
{
    /// <summary>
    /// Editor window for quickly swapping, rearranging, and other things to textures in Unity3D.
    /// </summary>
    public class QuickTextureEditor : EditorWindow
    {
        /// <summary>
        /// A list of the current affected textures.
        /// </summary>
        List<TextureInformation> texturePositionList;
 
        /// <summary>
        /// If true, the new texture's size will be forced to the nearest power of two.
        /// </summary>
        bool forcePowerOfTwo = false;
 
        /// <summary>
        /// Width of the new texture.
        /// </summary>
        int newTexWidth = 512;
 
        /// <summary>
        /// Height of the new texture.
        /// </summary>
        int newTexHeight = 512;
 
        /// <summary>
        /// The name of the new texture to be created.
        /// </summary>
        string newTextureName = "New Texture";
 
        /// <summary>
        /// Operations affecting different channels.
        /// </summary>
        public enum ChannelOperations
        {
            Ignore = 0,
            Set = 1,
            Add = 2,
            Subtract = 3,
            Multiply = 4,
            Divide = 5,
        }
 
        public struct ChannelBlendSetup
        {
            public ChannelOperations rCU, gCU, bCU, aCU;
        }
 
        /// <summary>
        /// Information about each texture being used to create the new texture.
        /// </summary>
        internal class TextureInformation
        {
            /// <summary>
            /// The texture being used.
            /// </summary>
            public Texture2D texture;
 
            /// <summary>
            /// The x and y position of the new texture.
            /// </summary>
            public int xPos, yPos;
 
            /// <summary>
            /// The x and y position of the new texture.
            /// </summary>
            public int width, height;
 
            /// <summary>
            /// Should a multiply color be used?
            /// </summary>
            public ChannelOperations blendColorUse = ChannelOperations.Ignore;
             
            /// <summary>
            /// The color to be blended with the texture.
            /// </summary>
            public Color blendColor;
 
            public ChannelBlendSetup rBS = new ChannelBlendSetup() { rCU = ChannelOperations.Set },
                gBS = new ChannelBlendSetup() { gCU = ChannelOperations.Set },
                bBS = new ChannelBlendSetup() { bCU = ChannelOperations.Set },
                aBS = new ChannelBlendSetup() { aCU = ChannelOperations.Set };
 
            public void OnGUI(string label, ref int refWidth, ref int refHeight)
            {
                if (texture != null)
                    label = texture.name;
                texture = (Texture2D)EditorGUILayout.ObjectField(label, texture, typeof(Texture2D), false);
 
                if (GUILayout.Button("Set as new texture size."))
                {
                    refWidth = width;
                    refHeight = height;
                }
 
                if (texture == null)
                {
                    Vector2 s = new Vector2(width, height);
                    s = EditorGUILayout.Vector2Field("Size", s);
                    width = Mathf.Max(1, Mathf.RoundToInt(s.x));
                    height = Mathf.Max(1, Mathf.RoundToInt(s.y));
                }
                else
                {
                    width = texture.width;
                    height = texture.height;
                }
 
                blendColorUse = (ChannelOperations)EditorGUILayout.EnumPopup("Blend Color Usage", blendColorUse);
                if (blendColorUse != ChannelOperations.Ignore)
                    blendColor = EditorGUILayout.ColorField(blendColor);
                else
                    blendColor = Color.white;
 
                Vector2 v = new Vector2(xPos, yPos);
                v = EditorGUILayout.Vector2Field("Pos", v);
                xPos = Mathf.RoundToInt(v.x);
                yPos = Mathf.RoundToInt(v.y);
 
                EditorGUILayout.BeginHorizontal();
 
                EditorGUILayout.BeginVertical();
                GUILayout.Label("");
                GUI.color = Color.red;
                GUILayout.Label("R");
 
                GUI.color = Color.green;
                GUILayout.Label("G");
 
                GUI.color = Color.blue;
                GUILayout.Label("B");
 
                GUI.color = Color.white;
                GUILayout.Label("A");
                EditorGUILayout.EndVertical();
 
                ChangeBlendSetup("R", ref rBS, Color.red);
                ChangeBlendSetup("G", ref gBS, Color.green);
                ChangeBlendSetup("B", ref bBS, Color.blue);
                ChangeBlendSetup("A", ref aBS, Color.white);
 
                EditorGUILayout.EndHorizontal();
            }
 
            private void ChangeBlendSetup(string p, ref ChannelBlendSetup bS, Color guiColor)
            {
                EditorGUILayout.BeginVertical();
                GUI.color = guiColor;
                GUILayout.Label(p);
                GUI.color = Color.white;
                 
                bS.rCU = (ChannelOperations)EditorGUILayout.EnumPopup(bS.rCU);
                bS.gCU = (ChannelOperations)EditorGUILayout.EnumPopup(bS.gCU);
                bS.bCU = (ChannelOperations)EditorGUILayout.EnumPopup(bS.bCU);
                bS.aCU = (ChannelOperations)EditorGUILayout.EnumPopup(bS.aCU);
                 
                EditorGUILayout.EndVertical();
            }
 
            internal void EditColor(ref Color colorOutput, ref Color colorInput)
            {
                EditChannel(ref colorOutput.r, ref colorInput, rBS);
                EditChannel(ref colorOutput.g, ref colorInput, gBS);
                EditChannel(ref colorOutput.b, ref colorInput, bBS);
                EditChannel(ref colorOutput.a, ref colorInput, aBS);
            }
 
            private void EditChannel(ref float outputValue, ref Color inputColor, ChannelBlendSetup bs)
            {
                EditChannel(ref outputValue, ref inputColor.r, bs.rCU);
                EditChannel(ref outputValue, ref inputColor.g, bs.gCU);
                EditChannel(ref outputValue, ref inputColor.b, bs.bCU);
                EditChannel(ref outputValue, ref inputColor.a, bs.aCU);
            }
 
            private void EditChannel(ref float output, ref float input, ChannelOperations channelUsage)
            {
                switch (channelUsage)
                {
                    case ChannelOperations.Set:
                        output = input;
                        break;
                    case ChannelOperations.Add:
                        output += input;
                        break;
                    case ChannelOperations.Divide:
                        output /= input;
                        break;
                    case ChannelOperations.Multiply:
                        output *= input;
                        break;
                    case ChannelOperations.Subtract:
                        output -= input;
                        break;
                    case ChannelOperations.Ignore:
                        return;
                }
            }
        }
 
         
 
        // Add menu named "My Window" to the Window menu
        [MenuItem("Tools/Quick Texture Editor")]
        static void Init()
        {
            // Get existing open window or if none, make a new one:
            QuickTextureEditor window = (QuickTextureEditor)EditorWindow.GetWindow(typeof(QuickTextureEditor));
            window.Show();
        }
 
        /// <summary>
        /// On GUI function that displays information in the editor.
        /// </summary>
        void OnGUI()
        {
            OnGUICombineTextures();
        }
 
        /// <summary>
        /// Quickly gets the importer of a specified asset
        /// </summary>
        /// <typeparam name="T">The type of importer to be used.</typeparam>
        /// <param name="asset">The asset whose importer is being referenced.</param>
        /// <returns>The importer, converted to the requested type.</returns>
        private T GetImporter<T>(UnityEngine.Object asset) where T : AssetImporter
        {
            return (T)AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(asset));
        }
 
        private void SetupList<T>(ref List<T> list, int p)
        {
            if (list == null)
                list = new List<T>();
            while (list.Count <= p)
                list.Add(default(T));
        }
 
        private T GetFromList<T>(ref List<T> list, int p)
        {
            SetupList(ref list, p);
            return list[p];
        }
 
        private void DefineTexturePose(int index)
        {
            SetupList(ref texturePositionList, index);
            if (texturePositionList[index] == null)
                texturePositionList[index] = new TextureInformation();
 
            texturePositionList[index].OnGUI("Texture " + index, ref newTexWidth, ref newTexHeight);
        }
 
        private static Color DivideColor(Color c)
        {
            return new Color(1f / c.r, 1f / c.g, 1f / c.b, 1f / c.a);
        }
 
        Vector2 scroll;
        private void OnGUICombineTextures()
        {
            // Defines information about the new texture.
            newTextureName = EditorGUILayout.TextField("New Texture Name", newTextureName);
 
            forcePowerOfTwo = EditorGUILayout.Toggle("Force Power of 2", forcePowerOfTwo);
            if (forcePowerOfTwo)
            {
                newTexWidth = Mathf.ClosestPowerOfTwo(EditorGUILayout.IntField("Width", newTexWidth));
                newTexHeight = Mathf.ClosestPowerOfTwo(EditorGUILayout.IntField("Height", newTexHeight));
            }
            else
            {
                newTexWidth = EditorGUILayout.IntField("Width", newTexWidth);
                newTexHeight = EditorGUILayout.IntField("Height", newTexHeight);
            }
 
            EditorGUILayout.Separator();
 
            scroll = EditorGUILayout.BeginScrollView(scroll);
            if (texturePositionList == null)
                texturePositionList = new List<TextureInformation>();
            for (int i = 0; i < texturePositionList.Count; i++)
            {
                DefineTexturePose(i);
            }
 
 
            EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("Add Texture"))
            {
                texturePositionList.Add(new TextureInformation());
                return;
            }
            if (GUILayout.Button("Remove Texture"))
            {
                texturePositionList.RemoveAt(texturePositionList.Count - 1);
                return;
            }
            EditorGUILayout.EndHorizontal();
 
            EditorGUILayout.EndScrollView();
 
            EditorGUILayout.Separator();
 
            if (GUILayout.Button("Save Texture"))
            {
                int textureCount = texturePositionList.Count;
 
                Texture2D newTex = new Texture2D(newTexWidth, newTexHeight);
                newTex.name = string.IsNullOrEmpty(newTextureName) ? "New Texture" : newTextureName; 
                Color[] mainColors = new Color[newTex.width * newTex.height];
                newTex.SetPixels(mainColors);
 
                List<TextureInformation> pulledTextures = new List<TextureInformation>();
                for (int i = 0; i < textureCount; i++)
                {
                    TextureInformation pos = GetFromList(ref texturePositionList, i);
                    if (pos == null)
                        continue;
                    else if (pos.texture == null)
                    {
                        pos.texture = new Texture2D(pos.width, pos.height);
                        pos.texture.name = "Texture " + i;
                        Color[] c = new Color[pos.width * pos.height];
                        for (int j = 0; j < c.Length; j++) c[j] = pos.blendColor; pos.texture.SetPixels(c); pos.texture.Apply(); } if (pos.texture.width + pos.xPos > newTex.width ||
                        pos.texture.height + pos.yPos > newTex.height)
                    {
                        Debug.LogWarning(pos.texture.name + " will not fit into new texture.  Skipping.");
                        continue;
                    }
 
                    pulledTextures.Add(pos);
                }
 
                for (int i = 0; i < pulledTextures.Count; i++)
                {
                    EditorUtility.DisplayProgressBar("Saving Texture", "Working on Texture " + i, (i + 1) / (pulledTextures.Count));
 
                    TextureImporter ti = GetImporter<TextureImporter>(pulledTextures[i].texture);
                    bool wasReadable = ti.isReadable;
                    bool wasNormal = ti.normalmap;
 
                    if (wasReadable != true)
                    {
                        ti.isReadable = true;
                        ti.SaveAndReimport();
                    }
 
                    if (wasNormal)
                    {
                        ti.normalmap = false;
                        ti.SaveAndReimport();
                    }
 
 
                    Color[] pulledColors = pulledTextures[i].texture.GetPixels();
 
                    if (pulledTextures[i].blendColorUse != ChannelOperations.Ignore)
                    {
                        for (int c = 0; c < pulledColors.Length; c++)
                        {
                            switch (pulledTextures[i].blendColorUse)
                            {
                                case ChannelOperations.Set:
                                    pulledColors = pulledTextures[i].blendColor;
                                    break;
                                case ChannelOperations.Add:
                                    pulledColors += pulledTextures[i].blendColor;
                                    break;
                                case ChannelOperations.Divide:
                                    pulledColors *= DivideColor(pulledTextures[i].blendColor);
                                    break;
                                case ChannelOperations.Multiply:
                                    pulledColors *= pulledTextures[i].blendColor;
                                    break;
                            }
                        }
                    }
 
                    Color[] colorsToModify =
                        newTex.GetPixels(pulledTextures[i].xPos, pulledTextures[i].yPos, pulledTextures[i].texture.width, pulledTextures[i].texture.height);
                     
                    // Adds these colors instead of setting.  Slower, but allows for combining channels or for combining reasons.
                    for (int c = 0; c < colorsToModify.Length; c++)
                        pulledTextures[i].EditColor(ref colorsToModify, ref pulledColors);
 
                    newTex.SetPixels(pulledTextures[i].xPos, pulledTextures[i].yPos, pulledTextures[i].texture.width, pulledTextures[i].texture.height,
                        colorsToModify);
 
                    if (ti.isReadable != wasReadable)
                    {
                        ti.isReadable = wasReadable;
                        ti.SaveAndReimport();
                    }
 
                    if (wasNormal)
                    {
                        ti.normalmap = true;
                        ti.SaveAndReimport();
                    }
                }
 
                SaveTexture(newTex);
 
                EditorUtility.ClearProgressBar();
            }
        }
 
        void SaveTexture(Texture2D texture2D)
        {
            byte[] bytes = texture2D.EncodeToPNG();
 
            File.WriteAllBytes(Application.dataPath + "/" + texture2D.name + ".png", bytes);
 
            AssetDatabase.Refresh();
        }
    }
}

If you use the script, credit would be nice. If you have any questions, feel free to ask here or on my twitter.


Starting “Over” & 2017 Thus Far

So I decided to “start over” with my blog. Blogger or Blogspot or whatever was becoming irritating to use and felt dated. The biggest issue is that writing code samples like this —

public class MyClass
{
    void Awake()
    {
        Debug.Log("Hello wor-, I mean planet.");
    }
}

— was a real pain.
Anyway, since this is the first blog that’ll appear on the official Mattrified Games website, I decided to do a quick retrospective of 2017 thus far.

MAGFest & Battle High 2 A+

In January, I went to my first MAGFest.  I went to show off Battle High 2 A+ as part of their independent games areas.  It was a great learning and motivating — to a degree — experience.  It was fun seeing people play the game and enjoying it.  There was even a Battle High 2 A+ tournament, which was awesome as well!  I could have definitely done a few things better; for example, not having an attract screen was probably not the best idea.  Also, I was at the booth so much, that it was hard to enjoy the festival itself; fortunately, it was 24 hour, so it wasn’t like it was impossible, but fatigue did set in a bit.

I did start a mailing list for Battle High 2 A+ and took it to another smaller and local Retro Games Festival.  There was also another tournament at ReplayFX.  Like I said, however, showing the game off was only motivating to a degree.  As much as I love the Battle High series, I’ve been working on it for a long time.  I’m not going to stop working on it entirely, but at this time, I’m pursuing different games and ideas.  There is still at least one Battle High 2 A+ character I would like to release, and there is still plenty of time to release said character before 2018, but I’m not going to promise it at this time.

The Aquatic Tactics Fighter

One game I’ve been developing off and on for awhile is a merfolk-themed tactics fighting game.  After MAGFest, I took a break from Battle High to work on this idea.  I really enjoy developing fighting games, but I wanted to develop something with more emphasis on story and single-player interactions.  So, for a bit, I was working on this a game that combined elements of a tactics RPG with those of a fighting game.  The problem, however, was that frakensteining the two genres together made me come to a few revelations.  One, it’s WAY too monumental of a task for a solo developer such as myself to take on.  Though hard, I don’t believe solo development is impossible, but for this game, trying to combine two large genres into one solid idea was intimidating.  At the same time, I was discovering that there are parts of tactics games I just don’t enjoy trying to develop or at least don’t feel inspired by.  So, due to these two issues, I decided to pause the idea indefinitely.

A New and True Fighting Game

I think one of my biggest regrets with Battle High is that I never took the time to try and develop a online multiplayer solution.  I felt very conflicted about the idea, ultimately deciding that the amount of time it would take to implement would be too much.  I’d probably would have never released.  So around the time I began losing passion for the Tactics Fighter, I discovered TrueSync by Exit Games.  This rollback netcode solution was made for Unity and though it’s still in beta, it’s giving me rather promising results.

Now, I’ve yet to really develop anything solid with it, but I’m confident that I can get something sooner than the Tactics Fighter.  In fact, I even signed up to give a talk at Unite 2017.  I really feel that TrueSync does a great job democratizing one of the more challenging aspects of online multiplayer for action games in a clear, easy-to-understand approach.

Anyway, my year so far started with Battle High, continued with the Tactics Fighter, and will probably end with a TrueSync fighter.  I’m hoping to release an alpha of some kind before 2018, before the fall actually.  I also plan to write more blogs here in the future; again, I hadn’t been keeping up to date with it because writing code samples, managing images, headings, and more was just a pain.  Hopefully in this new format, keeping my game work in one official place will be more manageable — again, hopefully.