Jump to content
Sign in to follow this  
  • entries
    7
  • comments
    0
  • views
    9915

Unreal Posting Trello Cards from a Running UE4 Game

ShawnGreer

870 views

UE4-Trello.jpg

This tutorial will give a step-by-step guide for creating Trello cards from inside a running Unreal Engine 4 project. While you can use this for just about anything you can think of, I personally use this as a way for my players to post bugs from inside the game. If you're not familiar with Trello, I highly suggest you check it out. It's a great tool for project management and organization. It should go without saying, but you're going to need an account on Trello in order to make any progress here.

I also want to mention at this point that this will be a C++ tutorial. Without some form of additional plug-in or other engine modification, you can't make the needed HTTP Request calls. Also, setting this up will assuming you know how to add input mappings and have worked with Widgets a bit.

Note: This was created UE4 4.17.

This article was originally published Ding! Games.

We're going to start simple, but I'll talk about a few ideas for adding in some variance toward the bottom.

Part I: Trello

We're going to start by going through some steps to authorize access to your Trello account and get the information we need in order to post new cards onto the board of your choice. In the end, we're looking to make an HTTP request to Trello that looks something like this (the italicized parts surrounded by {} are what you'll end up replacing with your own values).

https://api.trello.com/1/cards?key={Your+key}&token={yourtoken}&name={New+card+name}&desc={New+card+description}&idLavels={Label}&idList={list}

Step 1: Key

The first thing to do is generate a key with your Trello account. This will let you start to get the rest of the required items. While logged into Trello, go to the following link:

https://trello.com/app-key

Step 2: Token

The next step is to authorize write access. Copy the below address, but replace {Your+Key} with the key you got from Step 1 (make sure you take out the {} as well).

https://trello.com/1/authorize?key={Your+Key}&scope=read%2Cwrite&name=My+Application&expiration=never&response_type=token

Step 3: Board Id

Now you need to get the id of the actual board you want your cards to get posted to. Use the following link (no modifications needed):

https://trello.com/1/members/me/boards?fields=name

Exactly how this looks will depend on which Web Browser you're using. For example, Chrome will just spit out all of the text without organizing it at all, while Firefox will sort and color the data for you. Either one is fine, just look for the long string of alpha-numeric characters right after the name of the board you're specifically looking for.

This id isn't actually needed in the HTTP request line, but you need it to get the next two ids...

Step 4: List

Next up, the list within the board you want the cards to post to. As before copy the below, but replace the board id with the id you got from Step 3.

https://api.trello.com/1/boards/{board+id}/lists

Again, how it breaks it out will be determined by what Web Browser you're using. This is where using something like Firefox will make it a lot easier to pick out the exact one you're looking for. This time, the id (not the pos) you need is before the name of the list you want to post to. Also, make sure you don't inadvertently copy the board id again.

Just one more...

Step 5: Label

Last, but not least, is the label you want put on the card itself when it gets posted. This should be getting familiar, replace the board id in the address with your own.

https://api.trello.com/1/boards/{board+id}/labels

As with step 4, this can be a bit messy if your browser doesn't break it out for you, but you should be getting the hang of it by name. Grab the id (first entry per set) for the color you want your bards to post as.

Okay, hopefully you copied all those ids down somewhere, because you're going to need them for the next part, actually writing out the code.

Part II: C++ Code

Now that we've got all of the various ids that we need, it's time to dive into doing some code.

Step 1: HTTP Module

First up, we need to make a small edit to the build file, and include the HTTP module.

public class Zeus : ModuleRules
{
	public Zeus(ReadOnlyTargetRules Target) : base(Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

Step 2: Header Definition

Now, on to the actual implementation. I added my logic in the Game Mode, but you can actually put it just about anywhere you want. I'm also going to show a very simplistic setup, although I'll give some ideas on how to expand upon it at the bottom.

#pragma once

#include "ZeusFunctionLibrary.h"
#include "StandardPlayerController.h"
#include "Http.h"

#include "GameFramework/GameMode.h"
#include "ZeusGameMode.generated.h"

UCLASS()
class ZEUS_API AZeusGameMode : public AGameMode
{
	GENERATED_BODY()
	
public:
	AZeusGameMode();
	
	/** Used to report a bug */
	UFUNCTION(BlueprintCallable, Category = "Trello")
	void ReportBug(FString Name, FString Description);

	UFUNCTION(BlueprintImplementableEvent, Category = "Trello")
	void ReportBugComplete(bool bWasSuccessful);

private:
	void OnReportBugComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	
};

There are three functions defined in the above code:
  1. ReportBug: My goal is to have bugs reported from the game's GUI, so I wanted to be able to have the actual reporting of the bug called from blueprints. This function is passing the Name of the bug, and the full description (both entered by the player). You can modify this as you see fit.
  2. ReportBugComplete: This is implemented more for testing purposed. It's used to push the result of the bug submission back up to Blueprints to be handled. You can modify this as desired, or just remove it entirely.
  3. OnReportBugComplete: This is the function that gets automatically called after your HTTP request is processed. As such, it has to stay as is.

Now to the best part, submitting the card!

Step 3: Implementation

Here's the code:

#include "Zeus.h"
#include "ZeusGameMode.h"

const FString TrelloCards = "https://api.trello.com/1/cards?";

const FString Key = "1234567890abcdefghijklmnopqrstuv";
const FString Token = "123456789abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrst";
const FString Label = "123456789abcdefghijklmno";
const FString List = "123456789abcdefghijklmno";

AZeusGameMode::AZeusGameMode()
{
	// Nothing extra needed in the constructor for this
}

void AZeusGameMode::ReportBug(FString Name, FString Description)
{
	TSharedRef Request = FHttpModule::Get().CreateRequest();
	Request->SetVerb("POST");

	//Replace all the spaces with +
	Name = Name.Replace(TEXT(" "), TEXT("+"));
	Description = Description.Replace(TEXT(" "), TEXT("+"));
	
	//Construct the HTTP url
	FString URL = TrelloCards +
		"key=" + Key +
		"&token=" + Token +
		"&name=" + Name +
		"&desc=" + Description + "+(V:+" + UZeusFunctionLibrary::GetProjectVersion() + ")" +
		"&idList=" + List +
		"&idLabels=" + Label;

	// Set the built URL
	Request->SetURL(URL);
	// Bind OnReportBugComplete() to be called once the request is complete
	Request->OnProcessRequestComplete().BindUObject(this, &AZeusGameMode::OnReportBugComplete);
	// Process the request
	Request->ProcessRequest();
}

void AZeusGameMode::OnReportBugComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	// Calls up to a Blueprint implemented event
	ReportBugComplete(bWasSuccessful);
}

All of the logic takes place in the ReportBug function, essentially going through the following steps:
  1. Create the request and set it to POST (This is an HTTP protocol thing)
  2. Change all of the spaces to + (Another HTTP protocol thing)
  3. Build out the full string to be reported, using the model I posted way up top. Note that I have a function in there that adds on the current project version to the description. This is very handy for tracking bugs, as you really want to know what version someone is playing, without having to worry about them typing it in themselves somwhere.
  4. Set the built URL against the Request, bind the OnReportBugComplete() function and then process the request.

That's it for the code level. This alone gives you a good bit of freedom for what to do, but I'll show you a simple implementation at the Blueprint level next.

Phase 3: Blueprints

Step 1: Widget

First, lets build out our Widget that the player will actually use to input their bug:

Widget-Hierarchy.png

Bug-Report-Screen.png

I was looking for functional when I threw this together, not pretty. It's an extremely simplistic Widget, with some texts boxes for the user to input a title and description of the bug, and then either Submit, or Cancel.

The buttons have the following attached to their respective OnClick() events:

Submit:

Click-Submit-Button.png

Cancel:

Click-Cancel-Button.png

Step 2: Input Mapping

Under project Settings->Input, we want to add an action mapping to start of this whole series of events:

Action-Mapping.png

Step 3: Report Bug Event

Last, but not least, we need to fill out the logic for when the user actually presses F12. I put this in the Blueprinted version of my Player Controller, but you can put this in anything that receives user input:

InputAction-ReportBug.png

A couple of things to note here is that I automatically take a screenshot and pause the game. Neither or required, but I find both to be handy.

Conclusion

And there you have it! While running, you should now be able to press F12, create a bug report and submit it to Trello, where it should appear almost instantly.

Now, as I mentioned earlier, this is a rather straightforward approach that has the potential for expansion. For example, as it stands, the Board, List and Labels are currently hard coded values, meaning that you always post to the same place with the same label marking it. You could expand upon that by putting in a drop-down for the play to choose how critical of a bug they found, passing that as a variable down to the ReportBug() method (such as in the form of an enum), which you could then use to select which label gets attached.

You could also let the player submit recommendations or feedback in addition to bug reports, which could result in the cards being posted to a different list, or completely different board. The variations are endless, but ultimately, anything that lets the player quickly provide some form of feedback without having to leave the game or do something else is ideal.

Hopefully you don't have any trouble getting any of the above implemented, but if you do, please feel free to post your questions, and I'll see what I can do.

Best of luck!

Shawn

Shawn is the Designer/Developer at Ding! Games. They are currently working on a game code-named Zeus, a RPG Action/Puzzle game. For more great articles or to get the latest news bout game development, visit them at their site.



0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By CommanderLake
      I've been experimenting with my own n-body simulation for some time and I recently discovered how to optimize it for efficient multithreading and vectorization with the Intel compiler. It did exactly the same thing after making it multithreaded and scaled very well on my ancient i7 3820 (4.3GHz). Then I changed the interleaved xy coordinates to separate arrays for x and y to eliminate the strided loads to improve AVX scaling and copy the coordinates to an interleaved array for OpenTK to render as points. Now the physics is all wrong, the points form clumps that interact with each other but they are unusually dense and accelerate faster than they decelerate causing the clumps to randomly fly off into the distance and after several seconds I get a NaN where 2 points somehow occupy exactly the same x and y float coordinates. This is the C++ DLL:
      #include "PPC.h" #include <thread> static const float G = 0.0000001F; void Compute(float* pointsx, float* pointsy, float* points, float* velx, float* vely, long pcount, float aspect, float zoom) { #pragma omp parallel for for (auto i = 0; i < pcount; ++i) { auto fx = 0.0F; auto fy = 0.0F; for (auto j = i + 1; j < pcount; ++j) { const auto dx = pointsx[i] - pointsx[j]; const auto dy = pointsy[i] - pointsy[j]; //if(px != px) continue; //most efficient way to avoid a NaN failure const auto f = G / (dx * dx + dy * dy); fx += dx * f; fy += dy * f; } for (auto k = 0; k < i; ++k) { const auto dx = pointsx[i] - pointsx[k]; const auto dy = pointsy[i] - pointsy[k]; //if (px != px) continue; const auto f = G / (dx * dx + dy * dy); fx += dx * f; fy += dy * f; } pointsx[i] += velx[i] -= fx; pointsy[i] += vely[i] -= fy; if (zoom != 1) { //replacing this with a direct assignment as below does not help points[i * 2] = pointsx[i] * zoom / aspect; points[i * 2 + 1] = pointsy[i] * zoom; } else { points[i * 2] = pointsx[i] / aspect; points[i * 2 + 1] = pointsy[i]; } /*points[i * 2] = pointsx[i]; points[i * 2 + 1] = pointsy[i];*/ } } This is the relevant part of the C# OpenTK GameWindow:
      private void PhysicsLoop(){ while(true){ if(stop){ for(var i = 0; i < pcount; ++i) { velx[i] = vely[i] = 0F; } } if(reset){ var r = new Random(); for(var i = 0; i < Startcount; ++i) { retry: pointsx[i] = (float)(r.NextDouble() * 2.0F - 1.0F); pointsy[i] = (float)(r.NextDouble() * 2.0F - 1.0F); if(Math.Sqrt(pointsx[i]*pointsx[i] + pointsy[i]*pointsy[i]) > 1.0F) goto retry; velx[i] = vely[i] = 0.0F; } pcount = Startcount; buffersize = (IntPtr)(pcount*8); updatepcount = true; reset = false; } are.WaitOne(); NativeMethods.Compute(pointsx, pointsy, points0, velx, vely, pcount, aspect, zoom); var pointstemp = points0; points0 = points1; points1 = pointstemp; are1.Set(); } } protected override void OnRenderFrame(FrameEventArgs e){ if(updatepcount){ Title = pcount.ToString(); updatepcount = false; } GL.Clear(ClearBufferMask.ColorBufferBit); GL.EnableVertexAttribArray(0); GL.BindBuffer(BufferTarget.ArrayBuffer, vbo); mre1.Wait(); are1.WaitOne(); GL.BufferData(BufferTarget.ArrayBuffer, buffersize, points1, BufferUsageHint.StaticDraw); are.Set(); GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); GL.DrawArrays(PrimitiveType.Points, 0, pcount); GL.DisableVertexAttribArray(0); SwapBuffers(); } These are the array declarations:
      private const int Startcount = 4096; private readonly float[] pointsx = new float[Startcount]; private readonly float[] pointsy = new float[Startcount]; private float[] points0 = new float[Startcount*2]; private float[] points1 = new float[Startcount*2]; private readonly float[] velx = new float[Startcount]; private readonly float[] vely = new float[Startcount]; The compiled x64 AVX versions can be downloaded from my server here:
      How it should work:
      Single thread: http://commanderlake.net/PPsingle.7z Multithread: http://commanderlake.net/PPmulti.7z   Current broken version: http://commanderlake.net/PP.7z
    • By _WeirdCat_
      so I have 3 classes
      main class which stores a pointer
      struct Main { int * p; A subclass1, subclass2; void init() { subclass1.init(p); subclass2.init(p); } }; and two subclasses struct A { int * p; void init(int * ap) { p = ap; } }; Now I would like to create a pointer from subclass1 or subclass2 that will actually create a pointer in Main class, so subclasses can use the same int from main class
      heres what im trying to achieve
      in first scenario you see same windows so you see that on phone1 and phone2 theres a blue wireframed object that im trying to share between multiple windows, but when I change projection window I cant see it, so I need a shared pointer
      https://youtu.be/9HLDsrm7R2w
       
      I already messed with int ** p; for subclasses but im missing something (maybe because int * p in main class is 0)
      and I cant assign it by int ** subp = (*mainclass.p);
       
       
      if someone tells me that I could use shared ptr then I ask him to provide some example code for me cause I never used it and have no xp with it.
      However pointer to pointer c style would be the best approach for me thus theres something wrong in the code I dont fully understand so far, int * p in main class has to be 0 until its not created by subclass...
      maybe I somewhere use it where I shouldnt, well I just changed the code from * to ** and just switched from if (p ==0) return;
      to if ( (*p) == 0) return; etc.
      but theres few thousands lines I need to check so a short brief of handling such types would be appreciated..
    • By phil67rpg
      well I am able to get my sprites to rotate and move in all directions, I have drawn two plane sprites, I am also able to shoot a bullet in the up direction, I want to shoot bullets in all directions just like my plane rotates, I just need a hint on how to proceed, go easy on me this is new stuff to me. However I am making progress.
    • By Shaarigan
      Hey,
      I'm currently starting next iteration on my engine project and have some points I'm completely fine with and some other points and/or code parts that need refactoring so this is a refactoring step before starting to add new features. As I want my code to be modular to have features optional installed for certain projects while others have to stay out of sight, I designed a framework that starting from a core component or module, spreads features to several project files that are merged together to a single project solution (in Visual Studio) by our tooling.
      This works great for some parts of the code, naming the Crypto or Input module for example but other parts seem to be at the wrong place and need to be moved. Some features are in the core component that may belong into an own module while I feel uncomfortable splitting those parts and determine what stays in core and what should get it's own module. An example is Math stuff. When using the framework to write a game (engine), I need access to algebra like Vector, Quaternion and Matrix objects but when writing some kind of match-making server, I wouldn't need it so put it into an own module with own directory, build script and package description or just stay in core and take the size and ammount of files as a treat in this case?
      What about naimng? When cleaning the folder structure I want to collect some files together that stay seperated currently. This files are foir example basic type definitions, utility macros and parts of my Reflection/RTTI/Meta system (which is intended to get ipartially t's own module as well because I just need it for editor code currently but supports conditional building to some kind of C# like attributes also).
      I already looked at several projects and they seem to don't care that much about that but growing the code means also grow breaking changes when refactoring in the future. So what are your suggestions/ oppinions to this topic? Do I overcomplicate things and overengeneer modularity or could it even be more modular? Where is the line between usefull and chaotic?
      Thanks in advance!
    • By PlanetExp
      I've been trying to organise a small-medium sized toy game project to supports macOS, iOS and Windows simultaneously in a clean way. But I always get stuck when I cross over to the target platform. I'll try to explain,
      I have organised my project in modules like so:
       
      1. core c++ engine, platform agnostic, has a public c++ api
      2. c api bindings for the c++ api, also platform agnostic, this is actually part of 1 because its such a small project
      3. target platform bindings, on iOS and macOS this is in swift. Basically wraps the c api
      4. target platform code. This part just calls the api. Also in swift.
       
      So in all I have 4 modules going simultaneously, all compiled into a separate static libraries and imported into the next phase/layer. Am I even remotely close to something functional? I seem to getting stuck somewhere between 2 and 3 when I cross over to the target platform. In theory I would just need to call the game loop, but I always end up writing some logic up there anyway.
       
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!