Most files of our game is download during playing. If the download speed has no limit, there is not network traffic space left for the game play protocols... I tried to use the "DownloadHandlerScript", and provide a array of "preallocatedBuffer" to control the accept content size from web per frame, but it look like unuseful...Can anyone help me please?
Here is the DownloadHandlerScript I use:
class CustomDownloadHandler : DownloadHandlerScript
{
private byte[] _cacheBytes;
private int _index;
public CustomDownloadHandler(byte[] preallocatedBuffer) : base(preallocatedBuffer)
{
}
protected override void ReceiveContentLength(int contentLength)
{
_cacheBytes = new byte[contentLength];
}
// this method will be called once per frame to hand chunks of that data to script.
protected override bool ReceiveData(byte[] buffer, int dataLength)
{
if (buffer == null || buffer.Length <= 0)
return false;
Array.Copy(buffer, 0, _cacheBytes, _index, dataLength);
_index += dataLength;
return true;
}
//...
}
And I create an UnityWebRequest like this:
bytes = new byte[4096]; //As preallocated buffer.
webRequest = UnityWebRequest.Get("url/file.assetbundle");
webRequest.downloadHandler = new CustomDownloadHandler(bytes);
webRequest.SendWebRequest();
But the download speed is same whether I use the downloadhandler or not...
Related
I'm trying to build an audio player that gets the mp3 files from an external source along with logic for Play, Stop, Next and Prev buttons but I haven't figured out how to do it.
http://localhost:8383/Unity3d/Audio/BT - Paul Van Dyk - Namistai.mp3"
http://localhost:8383/Unity3d/Audio/Stevie Wonder - Skeletons.mp3
And then with C#, I'm opening a UnityWebRequest for the URI, save it into an AudioClip variable and then to an AudioSource variable but I'm then finding issues with using the length property/saving into some sort of Audio clip array.
Here's my code below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;
using UnityEngine.UI;
[RequireComponent(typeof(AudioSource))]
public class AudioManager : MonoBehaviour
{
public AudioClip[] musicClips;
private int currentTrack;
private AudioSource source;
public List<string> externalAudio;
IEnumerator GetAudioClip()
{
externalAudio.Add("http://localhost:8383/Unity3d/Audio/Stevie%20Wonder%20-%20Skeletons.mp3");
externalAudio.Add("http://localhost:8383/Unity3d/Audio/York%20-%20On%20The%20Beach.mp3");
externalAudio.Add("http://localhost:8383/Unity3d/Audio/BT%20-%20Paul%20Van%20Dyk%20-%20Namistai.mp3");
externalAudio.Add("http://localhost:8383/Unity3d/Audio/S.O.S.%20BAND-JUST%20BE%20GOOD%20TO%20ME%20(SINGLE).mp3");
externalAudio.Add("http://localhost:8383/Unity3d/Audio/Michael%20Jackson%20-%20Beat%20It%20(Official%20Video).mp3");
Debug.Log("Hello World");
Debug.Log(UnityWebRequestMultimedia.GetAudioClip("http://localhost:8383/Unity3d/Audio/Stevie%20Wonder%20-%20Skeletons.mp3", AudioType.MPEG));
foreach (var audioClip in externalAudio)
{
Debug.Log(audioClip);
using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(audioClip, AudioType.MPEG))
{
UnityWebRequest request = new UnityWebRequest();
yield return www.SendWebRequest();
if (request.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(www.error);
}
else
{
musicClips = DownloadHandlerAudioClip.GetContent(www);
source.clip = musicClips;
Debug.Log("Audio is playing.");
}
}
}
}
// Start is called before the first frame update
void Start()
{
source = GetComponent<AudioSource>();
//PLAY MUSIC
PlayMusic();
}
public void PlayMusic()
{
if (source.isPlaying)
{
return;
}
GetAudioClip();
currentTrack--;
if (currentTrack < 0)
{
currentTrack = 0;
}
StartCoroutine(WaitForMusicEnd());
}
IEnumerator WaitForMusicEnd()
{
while (source.isPlaying)
{
yield return null;
}
NextTitle();
}
public void NextTitle()
{
source.Stop();
currentTrack++;
if (currentTrack > musicClips.Length - 1)
{
currentTrack = 0;
}
source.clip = musicClips[currentTrack];
source.Play();
//Show title
StartCoroutine("WaitForMusicEnd");
}
public void PreviousTitle()
{
source.Stop();
currentTrack--;
if (currentTrack < 0)
{
currentTrack = musicClips.Length - 1;
}
source.clip = musicClips[currentTrack];
source.Play();
//Show title
StartCoroutine("WaitForMusicEnd");
}
public void StopMusic()
{
StopCoroutine("WaitForMusicEnd");
source.Stop();
}
}
How can I save the external audio into an AudioClip list and then use them on the play/stop functions logic?
So this is a little bit broad but here is what I would probably do.
As said let your server provide a list of URLs for the downloads. This way you don't have to hardcode them into your client.
So with the previous point, the first thing you do is one single normal GET request to receive that list of URLs, e.g. simply separated by \n (line breaks)
So the first string message you receive could look like e.g.
http://localhost:8383/Moonbeam%20Challenge/Audio/Stevie%20Wonder%20-%20Skeletons.mp3
http://localhost:8383/Moonbeam%20Challenge/Audio/York%20-%20On%20The%20Beach.mp3
http://localhost:8383/Moonbeam%20Challenge/Audio/BT%20-%20Paul%20Van%20Dyk%20-%20Namistai.mp3
http://localhost:8383/Moonbeam%20Challenge/Audio/S.O.S.%20BAND-JUST%20BE%20GOOD%20TO%20ME%20(SINGLE).mp3
http://localhost:8383/Moonbeam%20Challenge/Audio/Michael%20Jackson%20-%20Beat%20It%20(Official%20Video).mp3
This received list you Split into the individual URLs
var urls = urlList.Split('\n');
For these you start your individual GetAudioClip requests.
The results you store in an array/a list as elements. You don't assign the entire musicClips. I would rather use a
List<AudioClip> musicClips = new List<AudioClip>();
and then from your downloads do
musicClips.Add(DownloadHandlerAudioClip.GetContent(www));
you also don't want to assign and play each downloaded clip to the AudioSource as soon as it is downloaded but rather wait until all of them are downloaded (or at least the first one, depending on your needs)
So this might look somewhat like
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Networking;
using UnityEngine.UI;
using System.Linq;
[RequireComponent(typeof(AudioSource))]
public class AudioManager : MonoBehaviour
{
[Header("References")]
[SerializeField]
private AudioSource source;
[Header("Settings")]
[SerializeField] private string baseURL;
[SerializeField] private string[] externalAudio;
[SerializeField] private bool autoStartPlayAfterDownloads = true;
[Header("Debugging")]
[SerializeField] private List<AudioClip> musicClips = new List<AudioClip>();
[SerializeField] private int currentTrack;
[SerializeField] private bool isInitialized;
private Coroutine currentPlayTrack;
// Yes, if you make Start return IEnumerator then Units
// automatically runs it as a Coroutine
private IEnumerator Start()
{
// block input from the outside until this controller is finished with the downloads
isInitialized = false;
if (!source) source = GetComponent<AudioSource>();
// Here either use the approach with first receiving a list from the server
yield return GetAudioClipsInfo();
// OR if you already have the clip urls assigned via the Inspector use one of
yield return GetAudioClipsSequencial();
// OR
yield return GetAudioClipsParallel();
// allow to do things from this point on
isInitialized = true;
if(autoStartPlayAfterDownloads) PlayFirstTitle();
}
// This is the routine that downloads the URL list from the server
// then it starts the individual downloads
private IEnumerator GetAudioClipsInfo()
{
// Make the first request for receiving the list of URLs from the server
// If not using this but already assigning the urls
// via Inspector then simply directly use the "GetAudioClips" below
using (var request = UnityWebRequest.Get(baseURL))
{
yield return request.SendWebRequest();
switch (request.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogError($"Could not get list of clips! Error: {request.error}", this);
yield break;
}
var urlList = request.downloadHandler.text;
externalAudio = urlList.Split('\n');
// Here you can either go for the sequencial downloads
yield return GetAudioClipsSequencial();
// OR run them all parallel
yield return GetAudioClipsParallel();
}
}
// This version starts one download at a time, waits until it is done
// then starts the next
private IEnumerator GetAudioClipsSequencial()
{
musicClips.Clear();
foreach (var url in externalAudio)
{
Debug.Log(url);
using (var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.MPEG))
{
yield return www.SendWebRequest();
switch (www.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogError($"Could not get clip from \"{url}\"! Error: {www.error}", this);
continue;
}
musicClips.Add(DownloadHandlerAudioClip.GetContent(www));
}
}
}
// This version starts all downloads at once and waits until they are all done
// probably faster than the sequencial version
private IEnumerator GetAudioClipsParallel()
{
musicClips.Clear();
var requests = new List<UnityWebRequest>();
foreach (var url in externalAudio)
{
Debug.Log(url);
var www = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.MPEG);
// Start the request without waiting
www.SendWebRequest();
requests.Add(www);
}
// Wait for all requests to finish
yield return new WaitWhile(() => requests.Any(r => !r.isDone));
// Now examine and use all results
foreach (var www in requests)
{
switch (www.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogError($"Could not get clip from \"{www.url}\"! Error: {www.error}", this);
continue;
}
musicClips.Add(DownloadHandlerAudioClip.GetContent(www));
www.Dispose();
}
}
public void PlayFirstTitle()
{
if (!isInitialized) return;
if (source.isPlaying) return;
if (currentPlayTrack != null)
{
StopCoroutine(currentPlayTrack);
}
currentPlayTrack = StartCoroutine(PlayTrack(0));
}
private IEnumerator PlayTrack(int index)
{
// Make sure the index is within the given clips range
index = Mathf.Clamp(index, 0, musicClips.Count);
// update the current track to make next and previous work
currentTrack = index;
// get clip by index
var clip = musicClips[currentTrack];
// Assign and play
source.clip = clip;
source.Play();
// wait for clip end
while (source.isPlaying)
{
yield return null;
}
NextTitle();
}
public void NextTitle()
{
if (!isInitialized) return;
if (currentPlayTrack != null)
{
StopCoroutine(currentPlayTrack);
}
source.Stop();
currentTrack = (currentTrack + 1) % musicClips.Count;
currentPlayTrack = StartCoroutine(PlayTrack(currentTrack));
}
public void PreviousTitle()
{
if (!isInitialized) return;
if (currentPlayTrack != null)
{
StopCoroutine(currentPlayTrack);
}
source.Stop();
currentTrack--;
if (currentTrack < 0)
{
currentTrack = musicClips.Count - 1;
}
currentPlayTrack = StartCoroutine(PlayTrack(currentTrack));
}
public void StopMusic()
{
if (!isInitialized) return;
if (currentPlayTrack != null)
{
StopCoroutine(currentPlayTrack);
}
source.Stop();
}
}
Im new in developing so need some help for my game!
On my game I have 2 buttons one is "Play" and other "Level Select"
I stuck at the "Play" button, need to make a script that is always loading the highest level that is unlocked, not current but highest.
Here is the code that im using for level manager
public List<Button> levelButton;
public Sprite lockimage;
public bool delete;
private void Start()
{
int saveIndex = PlayerPrefs.GetInt("SaveIndex");
for (int i = 0; i < levelButton.Count; i++)
{
if (i <= saveIndex)
{
levelButton[i].interactable = true;
}
else
{
levelButton[i].interactable = false;
levelButton[i].GetComponent<Image>().sprite = lockimage;
}
}
}
public void LevelSelect()
{
int level = int.Parse(EventSystem.current.currentSelectedGameObject.name);
SceneManager.LoadScene(level);
}
public void PlayGame()
{
//code here
}
public void ResetGame()
{
PlayerPrefs.SetInt("SaveIndex", 0);
SceneManager.LoadScene(0);
}
public void DontResetGame()
{
SceneManager.LoadScene(0);
}
}
SceneManager.LoadScene(PlayerPrefs.GetInt("SaveIndex"));
edit: adding some info/context.
I realized that you set level buttons interactivity on the start functions based on the int save_index value you get from PlayerPrefs.
From there, I assumed that you could load the level directly using that same value on the PlayGame function.
Do note that the code I wrote will throw an error, if the "SaveIndex" key is not yet on PlayerPrefs
I'm working on a small program that can modify the animation at run time(Such as when you run faster the animation not only play faster but also with larger movement). So i need to get the existing animation, change its value, then send it back.
I found it is interesting that i can set a new curve to the animation, but i can't get access to what i already have. So I either write a file to store my animation curve (as text file for example), or i find someway to read the animation on start up.
I tried to use
AnimationUtility.GetCurveBindings(AnimationCurve);
It worked in my testing, but in some page it says this is a "Editor code", that if i build the project into a standalone program it will not work anymore. Is that true? If so, is there any way to get the curve at run time?
Thanks to the clearify from Benjamin Zach and suggestion from TehMightyPotato
I'd like to keep the idea about modifying the animation at runtime. Because it could adapt to more situations imo.
My idea for now is to write a piece of editor code that can read from the curve in Editor and output all necesseary information about the curve (keyframes) into a text file. Then read that file at runtime and create new curve to overwrite the existing one. I will leave this question open for a few days and check it to see if anyone has a better idea about it.
As said already AnimationUtility belongs to the UnityEditor namespace. This entire namespace is completely stripped of in a build and nothing in it will be available in the final app but only within the Unity Editor.
Store AnimationCurves to file
In order to store all needed information to a file you could have a script for once serializing your specific animation curve(s) in the editor before building using e.g. BinaryFormatter.Serialize. Then later on runtime you can use BinaryFormatter.Deserialize for returning the info list again.
If you wanted it more editable you could as well use e.g. JSON or XML of course
UPDATE: In general Stop using BinaryFormatter!
In the newest Unity versions the Newtonsoft Json.NET package comes already preinstalled so simply rather use JSON
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
public class AnimationCurveManager : MonoBehaviour
{
[Serializable]
public sealed class ClipInfo
{
public int ClipInstanceID;
public List<CurveInfo> CurveInfos = new List<CurveInfo>();
// default constructor is sometimes required for (de)serialization
public ClipInfo() { }
public ClipInfo(Object clip, List<CurveInfo> curveInfos)
{
ClipInstanceID = clip.GetInstanceID();
CurveInfos = curveInfos;
}
}
[Serializable]
public sealed class CurveInfo
{
public string PathKey;
public List<KeyFrameInfo> Keys = new List<KeyFrameInfo>();
public WrapMode PreWrapMode;
public WrapMode PostWrapMode;
// default constructor is sometimes required for (de)serialization
public CurveInfo() { }
public CurveInfo(string pathKey, AnimationCurve curve)
{
PathKey = pathKey;
foreach (var keyframe in curve.keys)
{
Keys.Add(new KeyFrameInfo(keyframe));
}
PreWrapMode = curve.preWrapMode;
PostWrapMode = curve.postWrapMode;
}
}
[Serializable]
public sealed class KeyFrameInfo
{
public float Value;
public float InTangent;
public float InWeight;
public float OutTangent;
public float OutWeight;
public float Time;
public WeightedMode WeightedMode;
// default constructor is sometimes required for (de)serialization
public KeyFrameInfo() { }
public KeyFrameInfo(Keyframe keyframe)
{
Value = keyframe.value;
InTangent = keyframe.inTangent;
InWeight = keyframe.inWeight;
OutTangent = keyframe.outTangent;
OutWeight = keyframe.outWeight;
Time = keyframe.time;
WeightedMode = keyframe.weightedMode;
}
}
// I know ... singleton .. but what choices do we have? ;)
private static AnimationCurveManager _instance;
public static AnimationCurveManager Instance
{
get
{
// lazy initialization/instantiation
if (_instance) return _instance;
_instance = FindObjectOfType<AnimationCurveManager>();
if (_instance) return _instance;
_instance = new GameObject("AnimationCurveManager").AddComponent<AnimationCurveManager>();
return _instance;
}
}
// Clips to manage e.g. reference these via the Inspector
public List<AnimationClip> clips = new List<AnimationClip>();
// every animation curve belongs to a specific clip and
// a specific property of a specific component on a specific object
// for making this easier lets simply use a combined string as key
private string CurveKey(string pathToObject, Type type, string propertyName)
{
return $"{pathToObject}:{type.FullName}:{propertyName}";
}
public List<ClipInfo> ClipCurves = new List<ClipInfo>();
private string filePath = Path.Combine(Application.streamingAssetsPath, "AnimationCurves.dat");
private void Awake()
{
if (_instance && _instance != this)
{
Debug.LogWarning("Multiple Instances of AnimationCurveManager! Will ignore this one!", this);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
// load infos on runtime
LoadClipCurves();
}
#if UNITY_EDITOR
// Call this from the ContextMenu (or later via editor script)
[ContextMenu("Save Animation Curves")]
private void SaveAnimationCurves()
{
ClipCurves.Clear();
foreach (var clip in clips)
{
var curveInfos = new List<CurveInfo>();
ClipCurves.Add(new ClipInfo(clip, curveInfos));
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var key = CurveKey(binding.path, binding.type, binding.propertyName);
var curve = AnimationUtility.GetEditorCurve(clip, binding);
curveInfos.Add(new CurveInfo(key, curve));
}
}
// create the StreamingAssets folder if it does not exist
try
{
if (!Directory.Exists(Application.streamingAssetsPath))
{
Directory.CreateDirectory(Application.streamingAssetsPath);
}
}
catch (IOException ex)
{
Debug.LogError(ex.Message);
}
// create a new file e.g. AnimationCurves.dat in the StreamingAssets folder
var json = JsonConvert.SerializeObject(ClipCurves);
File.WriteAllText(filePath, json);
AssetDatabase.Refresh();
}
#endif
private void LoadClipCurves()
{
if (!File.Exists(filePath))
{
Debug.LogErrorFormat(this, "File \"{0}\" not found!", filePath);
return;
}
var fileStream = new FileStream(filePath, FileMode.Open);
var json = File.ReadAllText(filePath);
ClipCurves = JsonConvert.DeserializeObject<List<ClipInfo>>(json);
}
// now for getting a specific clip's curves
public AnimationCurve GetCurve(AnimationClip clip, string pathToObject, Type type, string propertyName)
{
// either not loaded yet or error -> try again
if (ClipCurves == null || ClipCurves.Count == 0) LoadClipCurves();
// still null? -> error
if (ClipCurves == null || ClipCurves.Count == 0)
{
Debug.LogError("Apparantly no clipCurves loaded!");
return null;
}
var clipInfo = ClipCurves.FirstOrDefault(ci => ci.ClipInstanceID == clip.GetInstanceID());
// does this clip exist in the dictionary?
if (clipInfo == null)
{
Debug.LogErrorFormat(this, "The clip \"{0}\" was not found in clipCurves!", clip.name);
return null;
}
var key = CurveKey(pathToObject, type, propertyName);
var curveInfo = clipInfo.CurveInfos.FirstOrDefault(c => string.Equals(c.PathKey, key));
// does the curve key exist for the clip?
if (curveInfo == null)
{
Debug.LogErrorFormat(this, "The key \"{0}\" was not found for clip \"{1}\"", key, clip.name);
return null;
}
var keyframes = new Keyframe[curveInfo.Keys.Count];
for (var i = 0; i < curveInfo.Keys.Count; i++)
{
var keyframe = curveInfo.Keys[i];
keyframes[i] = new Keyframe(keyframe.Time, keyframe.Value, keyframe.InTangent, keyframe.OutTangent, keyframe.InWeight, keyframe.OutWeight)
{
weightedMode = keyframe.WeightedMode
};
}
var curve = new AnimationCurve(keyframes)
{
postWrapMode = curveInfo.PostWrapMode,
preWrapMode = curveInfo.PreWrapMode
};
// otherwise finally return the AnimationCurve
return curve;
}
}
Then you can do something like e.e.
AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
clip,
"some/relative/GameObject",
typeof<SomeComponnet>,
"somePropertyName"
);
the second parameter pathToObject is an empty string if the property/component is attached to the root object itself. Otherwise it is given in the hierachy path as usual for Unity like e.g. "ChildName/FurtherChildName".
Now you can change the values and assign a new curve on runtime.
Assigning new curve on runtime
On runtime you can use animator.runtimeanimatorController in order to retrieve a RuntimeAnimatorController reference.
It has a property animationClips which returns all AnimationClips assigned to this controller.
You could then use e.g. Linq FirstOrDefault in order to find a specific AnimationClip by name and finally use AnimationClip.SetCurve to assign a new animation curve to a certain component and property.
E.g. something like
// you need those of course
string clipName;
AnimationCurve originalCurve = AnimationCurvesManager.Instance.GetCurve(
clip,
"some/relative/GameObject",
typeof<SomeComponnet>,
"somePropertyName"
);
// TODO
AnimationCurve newCurve = SomeMagic(originalCurve);
// get the animator reference
var animator = animatorObject.GetComponent<Animator>();
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips
var clips = controller.animationClips;
// find the specific clip by name
// alternatively you could also get this as before using a field and
// reference the according script via the Inspector
var someClip = clips.FirstOrDefault(clip => string.Equals(clipName, clip.name));
// was found?
if(!someClip)
{
Debug.LogWarningFormat(this, "There is no clip called {0}!", clipName);
return;
}
// assign a new curve
someClip.SetCurve("relative/path/to/some/GameObject", typeof(SomeComponnet), "somePropertyName", newCurve);
Note: Typed on smartphone so no warranty! But I hope the idea gets clear...
Also checkout the example in AnimationClip.SetCurve → You might want to use the Animation component instead of an Animator in your specific use case.
I have recently been creating a game with tutorials. Unfortunately, they didn't cover a save score feature. Thanks to another user, I was able to figure out that I needed to use playerprefs. I watched tutorials online, but none of them were helpful. If you can, please help me!
Gold Per Sec Script:
using UnityEngine;
using System.Collections;
public class GoldPerSec : MonoBehaviour {
public UnityEngine.UI.Text gpsDisplay;
public Click click;
public ItemManager[] items;
void Start () {
StartCoroutine(AutoTick ());
}
void Update () {
gpsDisplay.text = GetGoldPerSec() + " Money Per Sec";
}
public float GetGoldPerSec() {
float tick = 0;
foreach (ItemManager item in items) {
tick += item.count * item.tickValue;
}
return tick;
}
public void AutoGoldPerSec() {
click.gold += GetGoldPerSec() / 10;
}
IEnumerator AutoTick() {
while (true) {
AutoGoldPerSec();
yield return new WaitForSeconds(0.10f);
}
}
}
Gold Per Click script:
using UnityEngine;
using System.Collections;
public class Click : MonoBehaviour {
public UnityEngine.UI.Text gpc;
public UnityEngine.UI.Text goldDisplay;
public float gold = 0.00f;
public int goldperclick = 1;
void Update () {
goldDisplay.text = "" + gold.ToString("F0");
gpc.text = "Money Per Click: " + goldperclick;
}
public void Clicked(){
gold += goldperclick;
}
}
My idea was for the game to save when the game is quit, and load as soon as you load the game back up. I am a complete beginner, if anyone can tell me how to do this, please tell me! Thanks! :D
You can use unity's existing functions to achieve this.
For saving data use unity's OnApplicationQuit function like this
void OnApplicationQuit() {
PlayerPrefs.SetFloat("key", value);
}
And for Restoring the values use unity's Awake function like this
void Awake(){
value = PlayerPrefs.GetFloat("key");
}
Please note that PlayerPrefs is an easy way to save data but also an very unsafe way. The player can easily manipulate his "goldValue" since it's just stored as an integer in some file on his device. PlayerPrefs should usually just be used for values the player can changed any way within in game, like volume setting etc.
EXAMPLE CODE
void Save()
{
string filename = "/filename.dat";
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath+filename);
bf.Serialize(file, goldValue); //Use can easily use e.g. a List if you want to store more date
file.Close();
}
bool Load()
{
string filename = "/filename.dat";
if (File.Exists(Application.persistentDataPath + filename))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + filename, FileMode.Open);
goldValue=(int) bf.Deserialize(file);
file.Close();
return true;
}
return false;
}
Add the following code to Click class:
void Awake()
{
LoadData();
}
void OnApplicationQuit()
{
SaveData();
}
void SaveData()
{
PlayerPrefs.SetFloat("gold",gold);
}
void LoadData()
{
gold = PlayerPrefs.GetFloat("gold",0f);
}
I would like to create a function in C# that takes a specific webpage and coverts it to a JPG image from within ASP.NET. I don't want to do this via a third party or thumbnail service as I need the full image. I assume I would need to somehow leverage the webbrowser control from within ASP.NET but I just can't see where to get started. Does anyone have examples?
Ok, this was rather easy when I combined several different solutions:
These solutions gave me a thread-safe way to use the WebBrowser from ASP.NET:
http://www.beansoftware.com/ASP.NET-Tutorials/Get-Web-Site-Thumbnail-Image.aspx
http://www.eggheadcafe.com/tutorials/aspnet/b7cce396-e2b3-42d7-9571-cdc4eb38f3c1/build-a-selfcaching-asp.aspx
This solution gave me a way to convert BMP to JPG:
Bmp to jpg/png in C#
I simply adapted the code and put the following into a .cs:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Forms;
public class WebsiteToImage
{
private Bitmap m_Bitmap;
private string m_Url;
private string m_FileName = string.Empty;
public WebsiteToImage(string url)
{
// Without file
m_Url = url;
}
public WebsiteToImage(string url, string fileName)
{
// With file
m_Url = url;
m_FileName = fileName;
}
public Bitmap Generate()
{
// Thread
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// Capture
var browser = (WebBrowser)sender;
browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ScrollBarsEnabled = false;
m_Bitmap = new Bitmap(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.BringToFront();
browser.DrawToBitmap(m_Bitmap, browser.Bounds);
// Save as file?
if (m_FileName.Length > 0)
{
// Save
m_Bitmap.SaveJPG100(m_FileName);
}
}
}
public static class BitmapExtensions
{
public static void SaveJPG100(this Bitmap bmp, string filename)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(filename, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static void SaveJPG100(this Bitmap bmp, Stream stream)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bmp.Save(stream, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageDecoders();
foreach (var codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
// Return
return null;
}
}
And can call it as follows:
WebsiteToImage websiteToImage = new WebsiteToImage( "http://www.cnn.com", #"C:\Some Folder\Test.jpg");
websiteToImage.Generate();
It works with both a file and a stream. Make sure you add a reference to System.Windows.Forms to your ASP.NET project. I hope this helps.
UPDATE: I've updated the code to include the ability to capture the full page and not require any special settings to capture only a part of it.
Good solution by Mr Cat Man Do.
I've needed to add a row to suppress some errors that came up in some webpages
(with the help of an awesome colleague of mine)
private void _Generate()
{
var browser = new WebBrowser { ScrollBarsEnabled = false };
browser.ScriptErrorsSuppressed = true; // <--
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
}
...
Thanks Mr Do
Here is my implementation using extension methods and task factory instead thread:
/// <summary>
/// Convert url to bitmap byte array
/// </summary>
/// <param name="url">Url to browse</param>
/// <param name="width">width of page (if page contains frame, you need to pass this params)</param>
/// <param name="height">heigth of page (if page contains frame, you need to pass this params)</param>
/// <param name="htmlToManipulate">function to manipulate dom</param>
/// <param name="timeout">in milliseconds, how long can you wait for page response?</param>
/// <returns>bitmap byte[]</returns>
/// <example>
/// byte[] img = new Uri("http://www.uol.com.br").ToImage();
/// </example>
public static byte[] ToImage(this Uri url, int? width = null, int? height = null, Action<HtmlDocument> htmlToManipulate = null, int timeout = -1)
{
byte[] toReturn = null;
Task tsk = Task.Factory.StartNew(() =>
{
WebBrowser browser = new WebBrowser() { ScrollBarsEnabled = false };
browser.Navigate(url);
browser.DocumentCompleted += (s, e) =>
{
var browserSender = (WebBrowser)s;
if (browserSender.ReadyState == WebBrowserReadyState.Complete)
{
if (htmlToManipulate != null) htmlToManipulate(browserSender.Document);
browserSender.ClientSize = new Size(width ?? browser.Document.Body.ScrollRectangle.Width, height ?? browser.Document.Body.ScrollRectangle.Bottom);
browserSender.ScrollBarsEnabled = false;
browserSender.BringToFront();
using (Bitmap bmp = new Bitmap(browserSender.Document.Body.ScrollRectangle.Width, browserSender.Document.Body.ScrollRectangle.Bottom))
{
browserSender.DrawToBitmap(bmp, browserSender.Bounds);
toReturn = (byte[])new ImageConverter().ConvertTo(bmp, typeof(byte[]));
}
}
};
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
tsk.Wait(timeout);
return toReturn;
}
There is a good article by Peter Bromberg on this subject here. His solution seems to do what you need...
The solution is perfect, just needs a fixation in the line which sets the WIDTH of the image. For pages with a LARGE HEIGHT, it does not set the WIDTH appropriately:
//browser.ClientSize = new Size(browser.Document.Body.ScrollRectangle.Width, browser.Document.Body.ScrollRectangle.Bottom);
browser.ClientSize = new Size(1000, browser.Document.Body.ScrollRectangle.Bottom);
And for adding a reference to System.Windows.Forms, you should do it in .NET-tab of ADD REFERENCE instead of COM -tab.
You could use WatiN to open a new browser, then capture the screen and crop it appropriately.