Showing posts with label Unity. Show all posts
Showing posts with label Unity. Show all posts

Sunday, April 23, 2017

Create a Rust Plugin Using Oxide


You guys want to mod some Rust? Yea me too! In this tutorial I will show you how to use the Oxide API to create a basic Rust plugin and upload it to your rust server. I will not be showing you how to setup a server but maybe with some requests I'll do that in another post. I wrote this plugin in C# using Visual Studio 2015. Oxide does support other language options like lua.

Some things you will need:
  • Visual Studio 2015 - The community edition is free. Technically you don't NEED it...a text editor will get you by but seriously recommend using an IDE instead.
  • An FTP Client - I'll be using FileZilla.
  • RustAdmin - Not required but it's nice way to get feedback from the server if you have log messages or errors.
At a high level, all we need to do is write a plugin, which is simply a .cs file, and upload it to the server. If you're a text editor user all you really need to concern yourself with is uploading the code file you create. VS2015 users might want to pull the git repo repository for Oxide which you can find at: https://github.com/OxideMod/Oxide so you can take advantage of other peoples hard work to give you intellisenseand code suggestions. Thanks Oxide team!

After pulling/downloading the code from git and opening the visual studio solution - navigate to the Rust plugins folder in the Solution Explorer. Here you will right click the Plugins folder and Add > New Item. Select the C# class option and give your file a name.



I'm calling my plugin "Lottery" (filename LotteryPlugin.cs). After a certain amount of time players will be prompted to play the lottery. They will guess a number between 1 and 10. If they guess correctly they will win a Supply Signal . If they guess incorrectly they will not be able to guess again until the next lottery and be awarded a Rock. The lottery will also have a length/duration, if no one guesses correctly in that timeframe the lottery will end and players will wait until the next lottery to play again.

Let's write some code!

First, our class needs to inherit from RustPlugin and be in the Oxide.Plugins namespace. Be sure to include the Info and Description attributes for your plugin. These attributes include the plugin name, author name, plugin version, and description.

using Oxide.Core.Libraries.Covalence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Lottery""Adam Massingale"0.1)]
    [Description("Plugin that gives a player signal flare when they guess the right number")]
    public class LotteryPlugin : RustPlugin
    {
        ...
    }
}

Next, there are some things worth noting. Oxide supports timers, I opted to use my own because they weren't quite what I needed. Next there are two server hooks and one attribute I use that are worth noting, Init(), OnTick(), and ChatCommand respectively.

Init is a hook that is called when the plugin is initialized. I'm using it for some initialization.

void Init()
{
    nextLotteryTime = Time.time + lotteryRate;
    guessedPlayerIds = new List<ulong>();
}

OnTick is simply an update function called every tick. I'm using it to run my timer logic.

void OnTick()
{            
    if(!isLotteryRunning && nextLotteryTime < Time.time)
    {
        StartLottery();
    }

    if(isLotteryRunning && lotteryEndTime < Time.time)
    {
        LotteryExpired();
    }
}

ChatCommand is the attribute I'm using on my function to callback when the client types /lottery in the chat window. As you can see this also passes args which are parameters of the command.


[ChatCommand("lottery")]
void LotteryCommand(BasePlayer player, string command, string[] args)
{
    ...
}


You'll also see in the code I'm using a few more API calls I'll quickly explain these.

PrintToChat sends a server chat message to the server.

PrintToChat("Lottery Time! Guess a number between 1 and 10 (example: /lottery 10");

Player.ChatMessage sends a chat message to that instance of the player it's called on.

player.ChatMessage("Invalid guess. Try something like /lottery 10");

ItemManager.CreateByItemID passes in an item definition which I'm resolving with ItemManager.FindItemDefinition which passes in a string id for the item (i.e. supply.signal).

Item item = ItemManager.CreateByItemID(ItemManager.FindItemDefinition(gift).itemid);


That's about it for the important parts of the code. The rest of the code is a bit of simple logic for the lottery. Here is the script in it's entirety.

using Oxide.Core.Libraries.Covalence;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Lottery""Adam Massingale"0.1)]
    [Description("Plugin that gives a player signal flare when they guess the right number")]
    public class LotteryPlugin : RustPlugin
    {
        const float lotteryRate = 25f//in seconds
        const float lotteryLength = 25f;
        float lotteryEndTime;
        float nextLotteryTime;
        int winningNumber = -1;
        bool isLotteryRunning;
        List<ulong> guessedPlayerIds; //list of playerids that have guessed

        //Called when a plugin is being initialized
        void Init()
        {
            nextLotteryTime = Time.time + lotteryRate;
            guessedPlayerIds = new List<ulong>();
        }

        void OnTick()
        {            
            if(!isLotteryRunning && nextLotteryTime < Time.time)
            {
                StartLottery();
            }

            if(isLotteryRunning && lotteryEndTime < Time.time)
            {
                LotteryExpired();
            }
        }

        void LotteryExpired()
        {
            PrintToChat("The lottery has expired. There were no winners. Better luck next time.");
            isLotteryRunning = false;
            nextLotteryTime = Time.time + lotteryRate;
            guessedPlayerIds.Clear();
        }

        void StartLottery()
        {
            isLotteryRunning = true;
            lotteryEndTime = Time.time + lotteryLength;
            winningNumber = UnityEngine.Random.Range(111);
            PrintToChat("Lottery Time! Guess a number between 1 and 10 (example: /lottery 10");
        }

        [ChatCommand("lottery")]
        void LotteryCommand(BasePlayer player, string command, string[] args)
        {
            if (!isLotteryRunning)
            {
                float delta = nextLotteryTime - Time.time;
                player.ChatMessage("The lottery has not started yet! Next lottery in " + delta + " seconds.");
                return;
            }

            if(guessedPlayerIds.Contains(player.userID))
            {
                float delta = nextLotteryTime - Time.time;
                player.ChatMessage("You've already played! Try again at the next lottery in " + delta + " seconds.");
                return;
            }

            if(args.Length != 1)
            {
                player.ChatMessage("Invalid guess. Try something like /lottery 10");
                return;
            }

            int guessedNumber;
            bool isNumeric = int.TryParse(args[0], out guessedNumber);
            
            if(!isNumeric)
            {
                player.ChatMessage("Your guess isn't a number! Try something like /lottery 10");
                return;
            }

            //WINNER
            if(isLotteryRunning && guessedNumber == winningNumber)
            {
                PrintToChat(player.displayName + " has won the lottery!");
                GivePlayerGift(player"supply.signal");
                isLotteryRunning = false;
                nextLotteryTime = Time.time + lotteryRate;
                guessedPlayerIds.Clear();
            }
            //LOSER
            else
            {             
                player.ChatMessage("You didn't win this time...Keep Playing!");
                GivePlayerGift(player"rock");                
                guessedPlayerIds.Add(player.userID);
            }
        }

        void GivePlayerGift(BasePlayer player, string gift)
        {
            Item item = ItemManager.CreateByItemID(ItemManager.FindItemDefinition(gift).itemid);
            player.GiveItem(item);
        }
    }
}

Now that we have our plugin written we just need to upload it to our server and we're done! You'll need the FTP information to use FileZilla to upload it to the server. If you use a decent hosting service this info should be provided to you. After you Quickconnect with these credentials via FileZilla you should see your servers file structure under the Remote site pane. Navigate to server>oxide>plugins. This is where you an drag and drop your plugin in the pane below. See the image below for clarification.


As you can see I'm trying out several other plugins that can be found on Oxides site. After you've uploaded your plugin. It will immediately be available in game. This plugin does not require a server restart...but there are likely some that do.


A quick piece of advice on debugging issues. RustAdmin is a tool that I use to identify issues in uploading my plugins. So if you're catching null reference exceptions etc it's great to get some insight on to what's going on.

Thanks for reading! Please post any questions in the comments and/or share if you liked this post!


Friday, March 31, 2017

Programming Weapon Sway

Weapon sway in games can easily be taken for granted by players. However, when it's not implemented the game looks plain and undone with a stationary weapon in the first person view. So that begs the question, how do first person shooters implement that cool weapon sway?! The truth is it's quite simple and this post will provide a brief code snippet on how to do it. Keep in mind that there are 1000 ways to skin a cat and the technique I'm presenting is one of those ways..but it is fairly popular.

Let's talk about Lissajous curves. There are plenty of websites that go into great detail about all the history, equations, and cool stuff you can do with Lissajous curves. I'll explain it simply and in terms of weapon sway. Think in your mind of your favorite FPS. As you walk the weapon typically sways horizontally and vertically in nice harmonic pattern. This complex motion in the x and y dimension is a Lissajous pattern.

Now, to program sway via Lissajous curves you can simply combine sin and/or cosine waves. There are different forms of the equations that use sin and/or cosine waves but for our example we will use 2 sin waves. Basically the goal is to oscillate vertically and horizontally harmoniously. From there you can customize your waves to get the effect you want.

This code will get you that traditional figure eight sway (this is a Unity C# snippet). Enjoy!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleWeaponSway : MonoBehaviour 
{

    [SerializeField]
    Transform weapon;

    [Range(01f)]
    [SerializeField]
    float verticalSwayAmount = 0.5f;

    [Range(01f)]
    [SerializeField]
    float horiztonalSwayAmount = 1f;

    [Range(015f)]
    [SerializeField]
    float swaySpeed = 4f;

    // Update is called once per frame
    void Update () 
    {
        float x = 0, y = 0;
        y += verticalSwayAmount * Mathf.Sin((swaySpeed * 2) * Time.time);
        x += horiztonalSwayAmount * Mathf.Sin(swaySpeed * Time.time);        
        weapon.position = new Vector3(xyweapon.position.z);
    }
}

Sunday, September 22, 2013

Project Xeno


This is an old post migrated from my previous blog (09/22/2013). I apologize if any info seems outdated.

Project Xeno is the latest Unity project I’ve been working on. It’s still in development but I really wanted to get some early screen shots of our progress so far. This game is an arcade style beat ‘em up/brawler. So far I’ve done all the gameplay and AI programming. Xeno (the protagonist) is able to engage in armed and unarmed melee combat with the ability to wield and dual-wield several different weapons. Xeno is capable of several different attack types including combo attacks, dash attacks, charged attacks, and aerial attacks. Weapons available include one-handed swords, two-handed swords, hammers, kamas, scythes, kunais, sai, daggers, lance, spear, staff, axes, and more. Here are some screenshots of the game thus far (below).


The cool art in this game was contributed by Jonathan Shook. Jon’s site:






Wednesday, August 21, 2013

Intro to Unity - Basic Platformer

This is an old post migrated from my previous blog (08/21/2013). I apologize if any info seems outdated.

Here is a simple platformer game I put together to teach people how to make a basic game in Unity for workshops presented at Microsoft in Alphretta, GA. Here is a link to download: Basic_Platformer
Please feel free to help yourself to the code I have provided in this starter kit. If you have any questions please contact me.

Monday, October 1, 2012

Fog of War

This is an old post migrated from my previous blog (10/01/2012). I apologize if any info seems outdated.

Many RTS games have a fog of war system. I want to share a portion of code that I used to accomplish this in Unity for the MOBA Project. I used a fairly basic approach and there are certainly more elegant ways to accomplish this. I will be showing how to calculated the pixel of intersection of a plane that is the fog of war.
First, I created the fog by placing a plane over the entire level. The plane has a dark translucent material in order to look something like fog. Then the idea is that; when a player or unit is between the fog and the camera, an area in the texture would be alphaed    out exposing that portion of the map.
To figure out the point in the texture between the unit/player and the camera I used a raytrace. Be sure to set up a mask, that way you only are casting against the plane that is the fog of war.
LayerMask mask = LayerMask.NameToLayer("FogOfWar");
RaycastHit hit;
mask=~mask;
if(Physics.Raycast(ray, out hit, dist, mask.value))
{//calc intersect pixel here}
Pixel data is stored as a 1D array. Only the texture coordinate information from the Raycast is available. What is important to is the actual pixel where the ray intersects with the fog of war plane. The code below is how to calculate that pixel from texture coordinate information.
if(hit.collider == null)
return;
Texture2D tex = hit.collider.renderer.material.mainTexture as Texture2D;
if(tex == null)
return;
int row = (int)(tex.width * hit.textureCoord.x); //row
int column = (int)(tex.height * hit.textureCoord.y);//col
 
//calc pixel in via text coords
int pixIn = (column * tex.width) - (tex.width - row);
//now alpha this pixel and an area around it
}
In the code above, pixIn and an area around it will be alphaed exposing the map below the fog.
From there it us up to you how to alpha the texture. I’m currently doing this procedurally with a buffer. I hope this helps someone getting started on a system like this. I’ll post some pics below of my first iteration. Please post any questions in the comments.



Wednesday, September 12, 2012

C to Unity Callback

This is an old post migrated from my previous blog (09/01/2012). I apologize if any info seems outdated.

This post will be sort of specific to those of you who may be writing/integrating an unmanaged API into Unity.
You might find yourself in a situation where some event or request may take place in your API and you need to notify Unity of this. One option is polling and that might be fine…but sometimes that can be sloppy, somewhat inefficient, or maybe just a poor way of solving your problem. Another option using callback.
In my simple example, the API is going to receive a network message then via function pointers we will call a function in Unity passing in the message . I’m actually going to be yanking some of this code directly out of my GummyNet API.
The first thing you want to do is define the type of function used for the callback. Notice that I’m passing the info, a string, as a parameter.
typedef void (__stdcall *OnGummyMessage_Callback)(char*);
You then need to set up a handler. We will be assigning our Unity function to this.
static OnGummyMessage_Callback OnGummyMessage;
You will also need a C function that Unity calls to assign the function pointer. It might look something like this:
EXPORT_API void SetCallback_OnGummyMessage(OnGummyMessage_Callback funcPtr)
{
 OnGummyMessage = funcPtr;
}
Now the Unity Side. Our objective here is to write the import for our set function. Declare delegate object (basically a  function pointer) that defines our method type instantiate it, and pass it to via our set function.
//Declaring the delegate
public delegate void OnGummyMessage_Callback(string msg);
 
//Instantiate the delegate and pass it
void RegisterCallbacks()
{
 OnGummyMessage_Callback onMsgCB = new OnGummyMessage_Callback(this.OnGummyMessage);
 SetCallback_OnGummyMessage(onMsgCB);
}
 
public void OnGummyMessage(string msg)
{
 print("receveied msg: " +msg);
}
 
//Importing the set function
[DllImport ("gummy_server")]
private static extern void SetCallback_OnGummyMessage(OnGummyMessage_Callback funcPtr);
Now when we Invoke our handler (shown below) in the API the C# OnGummyMessage should get called :)
...
//pass the message
OnGummyMessage((char*)data );
...

Friday, August 31, 2012

Procedural Skinned Mesh In Unity

This is an old post migrated from my previous blog (08/31/2012). I apologize if any info seems outdated.
In a current project I am faced with the challenge of needing to make tons of characters available to players. All the characters are of the same rig but are very complex with several systems such as: leg animator, gestures, animation groups, different sensory systems, ‘anchor transforms’ for weapons/etc…tons of stuff. So making a change to the smallest thing can be a real pain in the pipeline if there is a prefab for each character model variation.
So the ideal solution is have one character prefab and load in different models…easy right? Well it’s not so straight forward with systems that have to be baked on the fbx object and sometimes abstracting those systems to a higher level game object can be nearly impossible.
There are a couple ways to tackle this one. But I’m going to post some code particular to a procedural method of creating a character. It could use some optimization but it gets the point across :)
When you import an fbx with bones it should have a SkinnedMeshRenderer component on it. As far as coding goes there is a “sharedmesh” member — typeof(Mesh). This is the mesh with all the info that constructs it. The members of mesh we are concerned about are: vertices, uv, triangles, boneWeights, and bindposes. Unity actually has an example constructing an object from scratch that will help shed some light on what these are – EXAMPLE.
Here is my version of this process with a character. Please be aware at the end I use the bones from the source SkinnedMeshRenderer. You’ll want to copy and instantiate new bones and bind to those for a completely separate character…I just skipped that for my demo purposes because drawing the mesh on those bones was okay for me :)
Here is the code:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
//add this script to a gameobject...then be sure to set the source in inspector
public class Test : MonoBehaviour {
    //source models skinnedMeshRenderer
    public SkinnedMeshRenderer s;
    void Start()
        {
        //gameObject.AddComponent<animation>(); //using sources animation to test.
             gameObject.AddComponent<skinnedmeshrenderer>();
        SkinnedMeshRenderer renderer = GetComponent<skinnedmeshrenderer>();
              Mesh mesh = new Mesh();
        ////////////VERTS
        List<vector3> verts = new List<vector3>();
        for(int i = 0i &lts.sharedMesh.vertices.Lengthi++)
        {
            Vector3 v = s.sharedMesh.vertices[i];
            verts.Add(new Vector3(v.xv.yv.z));
        }
             mesh.vertices = verts.ToArray();
        ////////////UVS
        List<vector2> uvs = new List<vector2>();
        for(int i = 0i &lts.sharedMesh.uv.Lengthi++)
        {
            Vector2 uv = s.sharedMesh.uv[i];
            uvs.Add(new Vector2(uv.xuv.y));
        }
         mesh.uv = uvs.ToArray();
        ////////////TRIS
        List<int> tris = new List<int>();
        for(int i = 0i &lts.sharedMesh.triangles.Lengthi++)
        {
            int t = s.sharedMesh.triangles[i];
            tris.Add(t);
        }
        mesh.triangles = tris.ToArray();
        mesh.RecalculateNormals();
         renderer.material = new Material(Shader.Find(" Diffuse"));
        //BONE WEIGHTS
        List<boneweight> weights = new List<boneweight>();
        for(int i = 0i &lts.sharedMesh.boneWeights.Lengthi++)
        {
            BoneWeight b = new BoneWeight();
            b.boneIndex0 = s.sharedMesh.boneWeights[i].boneIndex0;
            b.weight0 = s.sharedMesh.boneWeights[i].weight0;
            b.boneIndex1 = s.sharedMesh.boneWeights[i].boneIndex1;
            b.weight1 = s.sharedMesh.boneWeights[i].weight1;
            b.boneIndex2 = s.sharedMesh.boneWeights[i].boneIndex2;
            b.weight2 = s.sharedMesh.boneWeights[i].weight2;
            b.boneIndex3 = s.sharedMesh.boneWeights[i].boneIndex3;
            b.weight3 = s.sharedMesh.boneWeights[i].weight3;
            weights.Add(b);
        }
        mesh.boneWeights = weights.ToArray();
             //BINDPOSES
        List<matrix4x4> bindPoses = new List<matrix4x4>();
        for(int i = 0i &lts.sharedMesh.bindposes.Lengthi++)
        {
            Matrix4x4 m = new Matrix4x4();
            Matrix4x4 mm = s.sharedMesh.bindposes[i];
            m.m00 = mm.m00;     m.m01 = mm.m01;     m.m02 = mm.m02;     m.m03 = mm.m03;
            m.m10 = mm.m10;     m.m11 = mm.m11;     m.m12 = mm.m12;     m.m13 = mm.m13
            m.m20 = mm.m20;     m.m21 = mm.m21;     m.m22 = mm.m22;     m.m23 = mm.m23;     
            m.m30 = mm.m30;     m.m31 = mm.m31;     m.m32 = mm.m32;     m.m33 = mm.m33
            bindPoses.Add(m);
        }
        mesh.bindposes = bindPoses.ToArray();
              //using sources bones...could make a copy here instead.
             renderer.bones = s.bones;
             renderer.sharedMesh = mesh;
        //play an animation here to test
        transform.parent.animation.Play("Walk");
    }
}