1. 程式人生 > 實用技巧 >計算機入門筆記

計算機入門筆記

版本控制

  1. 目前流行的兩大方式:SVN和Git; 前者採用增量式,後者採用快照式;
    前者產生單點故障影響大。

  2. 安裝git軟體。

  3. 本地庫——歷史版本; 暫存區——臨時儲存; 工作區——寫程式碼。

  4. 程式碼託管中心——維護遠端庫。

  5. 團隊內;

  6. 跨團隊。

  7. 本隊庫初始化

二、以Slider方式移動

  1. Slider Joint
    2D:讓一個物體沿著另一個物體轉;將此賦給空物體並勾選Kinematic選項讓其固定之後將需要移動的物體載入其中,並開啟Motor,設定其引數大於0,即可讓物體沿著空物體進行Slider形式移動。將下方指令碼掛載於空物體中並設定其最大距離即可產生來回運動效果。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

// Mount this script to the empty object that has the property of Slider joint
2d

// Do remember positioning the second/tail anchor point of slider joint 2D
vertical to each other!!!

public class TranslationLoop : MonoBehaviour

{

public SliderJoint2D sj2d; // Use properties in joint

public int upperTrans;

// Start is called before the first frame update

void Start()

{

sj2d = GetComponent<SliderJoint2D>(); //get the properties

sj2d.useLimits = true; //Use the limits of the slider joint 2D, make sure that
the translation limit is open

}

void FixedUpdate()

{

float maxStep = Mathf.Abs(Mathf.Sin(Time.time) * upperTrans); // Use sinus
mathematical calculation

// to get duration(-1,1); then calculate its Abs

JointTranslationLimits2D jt2D = new JointTranslationLimits2D();

jt2D.max = maxStep;

sj2d.limits = jt2D;

}

}

三、UI提示框

使用Canvas建立Button後對於Button按鍵新增Onclick事項必須將指令碼先賦給一個空物體再將其拖入OnClick列表中,勾選相應的函式即可實現。

(1)實現退出指令碼如下:

using UnityEngine;

using System.Collections;

public class BTNExit : MonoBehaviour {

public void BtnQuit()

{

#if UNITY_EDITOR

UnityEditor.EditorApplication.isPlaying = false;

#else

Application.Quit();

#endif

}

}

(2)實現遊戲暫停:

Time.timeScale = 0; 【當設定其為1時則為“繼續”】

(3)判斷點選值並據此彈框:

if (Input.GetKeyDown(KeyCode.Escape))

{ uiImage.SetActive(!uiImage.activeSelf); // The 'active' function returns the
result of status of object

if (uiImage.activeSelf) { Time.timeScale = 0; }

else { Time.timeScale = 1; } }

四、實現物體迴圈移動和旋轉

  1. 方法一思路:通過對時間的計算,每隔一段時間讓物體旋轉,實現來回移動。

floatTranslateSpeed=0.02f;

floatTranslateSpeedTime=0.1f;

voidUpdate(){

TranslateSpeedTime+=0.1f;

transform.Translate(Vector3.forward*TranslateSpeed);

if(TranslateSpeedTime>150.0f)

{

transform.Rotate(0,180,0);

TranslateSpeedTime=0.1f;

}

}

(1)首先給物體定義一個初始速度和初始的時間。

(2)然後使時間遞增。

(3)通過Translate函式使物體移動。

(4)Vector3.forward 是向前移動的意思,==Vector3(0,0,1)* Vector3.up 向上
具體可檢視API

(5)if判斷,規定一個時間,如若TranslateSpeedTime達到這個時間,讓物體沿著Y軸旋轉並且重置時間,繼續呼叫Update即可實現物體的重複移動並旋轉。

  1. 方法二:

public class BlutMov : MonoBehaviour

{

int timeMov = 0;

public int timeLasted;

private Vector2 objectPos;

// Use this for initialization

void Start()

{ objectPos = this.transform.position; // The original position of the object

}

void Update()

{

timeMov += 1;

transform.Translate(Vector2.right * Time.deltaTime);

if (timeMov == timeLasted*100)

{ this.transform.position = objectPos; timeMov = 0; } } }

  1. 方法三【使用如下外掛】:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

/// <summary>

/// Move back and forth

/// </summary>

public class Exercise : MonoBehaviour

{

public float xLength = 1; // The length of X array direction (Must be greater
than 0!)

public float yLength = 1; // The length of Y array direction (Must be greater
than 0!)

public float zLength = 1; // The length of Z array direction (Must be greater
than 0!)

public float xSpeed; // The speed of changing speed in X direction

public float ySpeed; // The speed of changing speed in Y direction

public float zSpeed; // The speed of changing speed in Z direction

public bool jointly; // Choose the moving way of the object

private float position;

// Update is called once per frame

void Update()

{

if (jointly)

{

transform.position = new Vector3(Mathf.PingPong( Time.time * xSpeed , xLength)
, Mathf.PingPong(Time.time * ySpeed , yLength) ,Mathf.PingPong(Time.time *
ySpeed , zLength));

// The function of PingPong in Mathf class can calculate out a floating result,
the first number can be 0 but must be a variable number while the second number
must be static number greater than 0.

}

else

{

transform.position = new Vector3(Mathf.Repeat( Time.time * xSpeed , xLength) ,
Mathf.Repeat(Time.time * ySpeed , yLength) ,Mathf.Repeat(Time.time * ySpeed ,
zLength));

}

}

}

五、鍵盤控制物體移動

三維場景下直接使用下方指令碼;二維場景下將Vector3中的Z軸引數設定為0,將Y軸引數設定為v即可。

指令碼如下:

using UnityEngine;

using System.Collections;

public class KingMov : MonoBehaviour

{

[Header(“speed name can be described here”)]

public float speed = 5;

private Transform transform;

public AudioClip testAu;

private AudioSource audio;

// Use this for initialization

void Start()

{

transform = this.GetComponent<Transform>();

audio = this.GetComponent<AudioSource>();

}

// Update is called once per frame

void Update()

{

float h = Input.GetAxis("Horizontal");

float v = Input.GetAxis("Vertical");

//transform.Translate(new Vector3(0.1f, 0, 0) * speed * 0.02f);

transform.Translate(new Vector3(h, v, 0) * speed * Time.deltaTime);

if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))

{

if (!audio.isPlaying) // These "if...else..."scripts can be abandoned while the
condition is "GetKeyDown"

{

audio.clip = testAu;

audio.Play();

}

}

else if(Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.UpArrow))

{ audio.Stop(); } }}

六、遊戲暫停用法

  1. 使用“Time.timeScale() =
    0;”可以暫停整個遊戲,但是注意,這樣的暫停方式對於與time模組無關的事務並不會起到任何效果,同時,尤其對於那些在Update函式裡面的一些並沒有用到time模組的程式碼仍然會繼續執行,由此可見,暫停只不過是暫停與時間相關的程式碼塊。

七、計時器在遊戲中及結束時的顯示

如將計時器用在“僅提示在玩家最後退出遊戲時”這一功能上時,應當注意在UI中選擇Canvas裡面的Canvas元件並將其通過enabled(false)方式關閉,不要直接使用SetActive(falses)函式關閉所Cavas有元件,這樣才能讓計時器暗自執行下去。

指令碼一:

using UnityEngine;

using System.Collections;

using UnityEngine.UI;

public class Timer : MonoBehaviour

{

float time, startTime;

Text timer;

void Start()

{

timer = GameObject.Find("Canvas/Timer").GetComponent<Text>();

startTime = Time.time; // Get the started time

}

void Update()

{

time = Time.time - startTime;

int seconds = (int)(time % 60);

int minutes = (int)(time / 60);

string strTime = string.Format("{0:00}:{1:00}", minutes, seconds); // Show in
format

timer.text = strTime;

}

}

八、使用指令碼控制動畫和音效

using UnityEngine;

using System.Collections;

public class KingMov : MonoBehaviour

{

public float speed = 5;

private Transform transform;

public AudioClip testAu;

private AudioSource audio;

// Use this for initialization

void Start()

{

transform = this.GetComponent<Transform>();

audio = this.GetComponent<AudioSource>();

}

// Update is called once per frame

void Update()

{

float h = Input.GetAxis("Horizontal");

float v = Input.GetAxis("Vertical");

//transform.Translate(new Vector3(0.1f, 0, 0) * speed * 0.02f);

transform.Translate(new Vector3(h, v, 0) * speed * Time.deltaTime);

if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))

{

if (!audio.isPlaying)

{

audio.clip = testAu;

audio.Play();

}

}

else if(Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.UpArrow))

{ audio.Stop(); } } }

九、遊戲退出

using UnityEngine;

using System.Collections;

public class BTNExit : MonoBehaviour {

public void BtnQuit()

{

#if UNITY_EDITOR

UnityEditor.EditorApplication.isPlaying = false;

#else

Application.Quit();

#endif

}

}

十、動畫物件根據按鈕的點選進行呈現

void Update()

{

if (Input.GetKeyDown(KeyCode.A))

{

GetComponent<Animator>().enabled = !GetComponent<Animator>().enabled;

GetComponent<SpriteRenderer>().enabled=
!GetComponent<SpriteRenderer>().enabled;

}

}

十一、使用暫停功能後的退回遊戲

public void ChangeScene(string sceneName)

{

SceneManager.LoadScene(sceneName);

Time.timeScale = 1; //暫停模式關閉

}

十二、通過Alpha值製作開機閃屏Logo

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class AlphaController : MonoBehaviour

{

// Update is called once per frame

void Update()

{

if (GetComponent<SpriteRenderer>().color.a > 0) //Get the alpha component
under the sprite render's color module

{

GetComponent<SpriteRenderer>().color -= new Color(0, 0, 0, 0.02f);
//Time.deltaTime);

}

}

}

十三、物體根據條件傳送

原理:

public gameobject position;

{void on tirggerenter ()...}

other.transforn.position = destination.position;

十四、射線檢測案例參考【二維】

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class RayCastDector : MonoBehaviour

{

public Transform detect_start;//檢測起點

public float player_max_distance;//檢測玩家的最大長度

public float max_degree;//檢測的最大角度

[HideInInspector] public bool detected_block = false;//是否檢測到障礙物

[HideInInspector] public bool detected_player = false;//是否檢測到玩家

private RaycastHit2D hit_info;//射線擊中資訊

private bool player_hit = false;//玩家是否撞上來

public GameObject Door;

/*每幀更新的部分*/

private void Update()

{

if (!player_hit)//如果玩家沒有主動撞上來

{

detected_player = detected_block = false;//假定什麼都沒有檢測到

}

for (detect_start.localEulerAngles = new Vector3(0, 0, 360 - max_degree);
detect_start.localEulerAngles.z >= (360 - max_degree) ||
detect_start.localEulerAngles.z <= max_degree; detect_start.localEulerAngles +=
new Vector3(0, 0, 1))//在敵人左右70度範圍內

{

if (hit_info = Physics2D.Raycast(detect_start.position, detect_start.up,
player_max_distance))

// Raycast(orign: , direction: , distance: ))

{

//The action that the other detected component will get. Below is an example of
making it black.

Door.GetComponent<SpriteRenderer>().color = Color.black;

}

else

{

Door.GetComponent<SpriteRenderer>().color = Color.white;

}

//Debug.DrawRay(detect_start.position, detect_start.up,Color.white,
player_max_distance);

}

}

}

十五、用指令碼控制Slider元件

在未使用指令碼執行時,slider元件預設接收左右按鍵來調整其填充量,倘要根據不同的情況讓填充量發生不同的變化,則需要通過指令碼來控制。下方只要將條件改變,即可改變填充條件。

void Update()

{

if (Input.GetKeyDown(KeyCode.O))

{

GetComponent<Slider>().value -= point;

}

else if (Input.GetKeyDown(KeyCode.P))

{

GetComponent<Slider>().value -= 25;

}

else if (Input.GetKey(KeyCode.UpArrow))

{

GetComponent<Slider>().value += 5;

}

else if (Input.GetKey(KeyCode.DownArrow))

{

GetComponent<Slider>().value -= 5;

}

}

十六、播放音樂

  1. 可以通過兩種方式在指令碼中播放音樂。第一種方法是:首先在自己建立的類中宣告一個公共AudioClip的一個音樂物件,之後再宣告一個公共的AudioSource的一個音源物件,後續根據相應條件播放音樂的時候在條件下方先指定音源的clip物件,再通過Play()函式開啟音源即可。

public AudioClip bgMusic;

private AudioSource audio;

------{

audio.clip = bgMusic;

audio.Play(); }

  1. 第二種方法是在上面方法的後半部分做調整,直接在宣告完相應的物件之後要播放時使用音源物件的PlayOneShot(音樂物件)函式來播放音樂。

public AudioClip bgMusic;

private AudioSource audio;

------{

audio.PlayOneShot(bgMusic); }

  1. audio.PlayDelayed(5.0f);則是在指定時間播放音樂,右邊表示在遊戲進行後的5秒中播放音樂。

  2. 必須要使用某個元件在指令碼內部註明後將會更加方便,也會有效避免誤刪元件。用

[RequireComponent(typeof(元件名稱))]即可。

【注意:通過private AudioSource
audio宣告音源之後一定記得在Start()函式中進行初始化。初始化寫法一般為——audio =
GetComponent<AudioSource>();。】

十七、初識協程

  1. 案例:

public void Select(GameStone c)

{

// Destroy(c.gameObject);

if (currentStone == null)

{

currentStone = c;

currentStone.isSelected = true;

return;

}

else

{

//The balls can only be exchanged while they are close to each other. The
mathematical calculation below shows that the indexes be compared under the
principle of the matrix, which is very
important!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

if (Mathf.Abs(currentStone.rowIndex - c.rowIndex) +
Mathf.Abs(currentStone.columIndex - c.columIndex) == 1)

{

// ExchangeAdMatches(currentStone, c);

StartCoroutine(ExchangeAdMatches(currentStone, c)); //呼叫協程函式的方式

}

currentStone.isSelected = false;

currentStone = null;

}

}

IEnumerator ExchangeAdMatches(GameStone q, GameStone p) //要帶有協程函式標識

// IEnumerator is used for making some of the functions wait or do something

{

Exchange(q, p);

yield return new WaitForSeconds(0.5f); //等待0.5秒之後再往下走

if (CheckHorizontalMathes() || CheckVerticalMatches())

{

RemoveSame();

}

else

{

Exchange(q, p);

}

}

【注意】

  1. 協程類似於執行緒,可以視為是一種偽執行緒。當程式中的程式碼在C#指令碼中執行到StartCoroutine();函式時,就會以非同步的方式直接繼續執行下面的程式碼,分立出來的StartCorutine()中的函式也會被執行,這就相當於fork出了另一個執行緒。該協程執行StartCorutine中得到函式時會找到以協程的方式命名的函式並在裡面執行,執行完必須會找到yield
    return程式碼段,並根據其返回的時間進行暫停或選擇直接繼續。但是這也是會執行一遍而已,故將協程的函式放到Update()函式中時,若不加以設定,其實就是沒有了實際的協程功效。處理方式如下:【通過下方程式碼可以實現計時器效果】

public class AudioController : MonoBehaviour

{

private bool IsFork = false;

// Update is called once per frame

void Update()

{

if (!IsFork)

{

StartCoroutine(VolumeController());

}

}

IEnumerator VolumeController()

{

IsFork = true;

yield return new WaitForSeconds(2);

Debug.Log(Time.time); // 需要在相應時間(2秒)後執行的程式碼

IsFork = false;

}

}

  1. 進一步瞭解Unity中協程(IEnumerator)的使用方法:【摘】

在Unity中,一般的方法都是順序執行的,一般的方法也都是在一幀中執行完畢的,當我們所寫的方法需要耗費一定時間時,便會出現幀率下降,畫面卡頓的現象。當我們呼叫一個方法想要讓一個物體緩慢消失時,除了在Update中執行相關操作外,Unity還提供了更加便利的方法,這便是協程。

在通常情況下,如果我們想要讓一個物體逐漸消失,我們希望方法可以一次呼叫便可在程式後續執行中實現我們想要的效果。

我們希望程式碼可以寫成如下所示:

void Fade()

{

for (float f = 1f; f >= 0; f -= 0.1f)

{

Color c = renderer.material.color;

c.a = f;

renderer.material.color = c;

}

}

然而該方法在呼叫時將在一幀中執行完畢,無法實現預期的效果。如果將該方法改寫並放到Update函式中可實現我們預期的效果,但是還不夠優雅。

float time = 0f;

float fadeTime = 2f;

void Fade()

{

time += Time.dealttime;

Color c = renderer.material.color;

c.a = 1f - time/fadeTime;

renderer.material.color = c;

}

Unity中的協程方法通過yield這個特殊的屬性可以在任何位置、任意時刻暫停。也可以在指定的時間或事件後繼續執行,而不影響上一次執行的就結果,提供了極大地便利性和實用性。

協程在每次執行時都會新建一個(偽)新執行緒來執行,而不會影響主執行緒的執行情況。

正如上邊的方法,我們使用協程可以更加方便的實現我們想要的效果。

void Fade()

{

for (float f = 1f; f >= 0; f -= 0.1f)

{

Color c = renderer.material.color;

c.a = f;

renderer.material.color = c;

yield return null;//下一幀繼續執行for迴圈

yield return new WaitForSeconds(0.1f);//0.1秒後繼續執行for迴圈

}

}

我們通過StartCoroutine()函式來呼叫協程函式。

值得注意的是,協程並不會在Unity中開闢新的執行緒來執行,其執行仍然發生在主執行緒中。當我們有較為耗時的操作時,可以將該操作分散到幾幀或者幾秒內完成,而不用在一幀內等這個操作完成後再執行其他操作。

如我們需要執行一個迴圈:

IEnumerator CaculateResult()

{

for (int i = 0; i < 10000; i++)

{

//內部迴圈計算

//在這裡的yield會讓改內部迴圈計算每幀執行一次,而不會等待10000次迴圈結束後再跳出

//yield return null;

}

//如果取消內部的yield操作,僅在for迴圈外邊寫yield操作,則會執行完10000次迴圈後再結束,相當於直接呼叫了一個函式,而非協程。

//yield return null;

}

呼叫協程的方法有兩種,分別是StartCoroutine(/這裡直接呼叫方法,新增引數/),另一種是StartCoroutine(/這裡填寫”字串的方法名字”,方法引數/)。第一種方法的優勢在於可以呼叫多個引數的方法,後一種方法只能呼叫不含引數或只包含一個引數的協程方法。但是第一種方法不能通過StopCoroutine(/這裡填寫”字串的方法名”/)來結束協程,只能通過StopAllCoroutines來結束。後一種則可以通過StopCoroutine來結束對正在執行的協程的呼叫。

協程在實現過程中我們需要注意yield呼叫的時機,執行較為複雜的計算時,如果在時間上沒有嚴格的先後順序,我們可以每幀執行一次迴圈來完成計算,或者每幀執行指定次數的迴圈來防止在程式執行中出現的卡頓現象。

yield return的介紹:

yield return null; // 下一幀再執行後續程式碼

yield return 0; //下一幀再執行後續程式碼

yield return 6;//(任意數字) 下一幀再執行後續程式碼

yield break; //直接結束該協程的後續操作

yield return asyncOperation;//等非同步操作結束後再執行後續程式碼

yield return
StartCoroution(/*某個協程*/);//等待某個協程執行完畢後再執行後續程式碼

yield return WWW();//等待WWW操作完成後再執行後續程式碼

yield return new
WaitForEndOfFrame();//等待幀結束,等待直到所有的攝像機和GUI被渲染完成後,在該幀顯示在螢幕之前執行

yield return new
WaitForSeconds(0.3f);//等待0.3秒,一段指定的時間延遲之後繼續執行,在所有的Update函式完成呼叫的那一幀之後(這裡的時間會受到Time.timeScale的影響);

yield return new
WaitForSecondsRealtime(0.3f);//等待0.3秒,一段指定的時間延遲之後繼續執行,在所有的Update函式完成呼叫的那一幀之後(這裡的時間不受到Time.timeScale的影響);

yield return WaitForFixedUpdate();//等待下一次FixedUpdate開始時再執行後續程式碼

yield return new WaitUntil()//將協同執行直到
當輸入的引數(或者委託)為true的時候....如:yield return new WaitUntil(() =>
frame >= 10);

yield return new WaitWhile()//將協同執行直到
當輸入的引數(或者委託)為false的時候.... 如:yield return new WaitWhile(() =>
frame < 10);

當某一個指令碼中的協程在執行過程中,如果我們將該指令碼的enable設定為false,協程不會停止。只有將掛載該指令碼的物體設定為SetActive(false)時才會停止。

Unity在呼叫StartCoroutine()後不會等待協程中的內容返回,會立即執行後續程式碼。

雖然協程十分方便和靈活,但不當的使用會使程式產生無法預想的後果,請使用前慎重考慮。

十八、選中得到物體改變顏色

  1. 先宣告一個SpriteRender類下的物件,該物件具有顏色屬性、Alpha屬性等,涉及到改變顏色時使用到其顏色屬性即可。

public SpriteRenderer render;

public bool isSelected

{

set

{

if (value)

{

render.color = Color.red;

}

else

{

render.color = Color.white;

}

}

}

  1. 當只使用get引數設定相應項時(必須去除set引數設定項),定義的該布林變數在外部無法在外部只能讀取而無法被修改,可以獲取其內部的值賦給其他變數而不能被重新使用。注意返回值的設定,後續使用時直接使用其返回值。

十九、滑鼠事件獲取

使用 public void OnMouseDown()

{

-----------------

}

函式可以獲取滑鼠事件,在裡面加入相應的觸發事件。

當獲取滑鼠離開某個區域時需要用到 public OnMouseExit(){}函式。
在使用這些函式的時候必須要指定collider區域才能夠在該區域發生相應動作。

案例(滑鼠進入後放音樂):

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[RequireComponent(typeof(BoxCollider2D))]

public class DectectMouse : MonoBehaviour

{

public AudioClip dangerMusic;

private AudioSource audio;

void Start()

{

audio = GetComponent<AudioSource>();

}

void OnMouseEnter()

{

audio.PlayOneShot(dangerMusic);

}

void OnMouseExit()

{

audio.Stop();

}

}

二十、讓物體跟蹤滑鼠軌跡/滑鼠游標消失

  1. 將要跟蹤滑鼠的圖片掛載在指令碼上即可。該指令碼需要掛載在相機上!

public class MouseFollower : MonoBehaviour

{

private Vector2 mousePos;

public GameObject mousePhoto;

private void Start()

{

Cursor.visible = false; //讓滑鼠游標消失

}

void Update()

{

mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);

mousePhoto.transform.position = new Vector3(mousePos.x, mousePos.y,
mousePhoto.transform.position.z) ;

// Using new vector3 position to make sure the position of the mouse. Make the Z
array's position change always attach to the object itself.

}

}

二一、從外部獲取某個物體的某個元件

案例用法:

Camera.main.GetComponent<MouseFollower>().enabled = false; 。

二二、計時器

  1. 直接自己撰寫一個計時器:

private float timer; // 計時器初始化

if(timer < 0)// 計時結束

{

CmdFire(); // events that will happen some time later

// 此處撰寫需要在計時之後執行的程式碼

timer = 1;

}

else// 計時開始

{

timer -= Time.deltaTime;// 每次自減一個時間單位(秒)

}

  1. 【通過下方程式碼可以實現計時器效果,上方《協程初始》中以有提及】

private bool IsFork = false;

// Update is called once per frame

void Update()

{

if (!IsFork)

{

StartCoroutine(VolumeController());

}

}

IEnumerator VolumeController()

{

IsFork = true;

yield return new WaitForSeconds(2);

Debug.Log(Time.time); // 需要在相應時間(2秒)後執行的程式碼

IsFork = false;

}

}

【推薦!!!!!!!!!!!!!!!!!!!!!!】

  1. 直接使用內部函式,最為方便的方法。注意Invoke()函式分別在Start()函式中和Update()函式中的使用方法,略有不同,如果在Update()函式中不進行如下判斷性質的設定,則計時也會失效。

  2. 在Start()函式中直使用:

void Start()

{

InvokeRepeating("SetVolum",0,2); //引號內部的為需要間隔一定時間執行的函式

//第二個引數為起始時間,第三個引數為時間間隔,以秒為單位

}

  1. 在Update()函式中執行時需要使用自帶的isInvoking()函式判斷其是否處在執行狀態:

void Update()

{

if (!IsInvoking())

{

Invoke("SetVolum",2);

}

}

二三、除錯

  1. 使用Debug.Log(“除錯提示語”);可以通過結果視窗中的提示語句進行除錯。

  2. 使用Debug.Break();可以直接讓程式在執行到此處時暫停。

二四、Mathf類中的函式群

  1. Mathf.SmoothDamp :
    平滑緩衝,東西不是僵硬的移動而是做減速緩衝運動到指定位置。

  2. 案例指令碼【通過緩慢改變位置實現緩慢跟蹤效果】:

public Transform target; //The player(跟蹤的目標)

public float smoothTime= 0.3f; //Smooth
Time(緩衝時間,時間越大緩衝速度越慢,移動也越慢)

private Vector2 velocity; //Velocity 承載的變數,相對緩衝減速

void Update ()

{

//Set the position

transform.position = new

Vector3( Mathf.SmoothDamp(transform.position.x, target.position.x, ref
velocity.x, smoothTime), Mathf.SmoothDamp( transform.position.y,
target.position.y, ref velocity.y, smoothTime), transform.position.z);
//三維遊戲中可以相應加入Z軸的緩衝

}

  1. Mathf.CeilToInt : 最小整數。返回最小的整數大於或等於f。

  2. Mathf.Ceil : 上限值。 返回 f
    指定數字或表示式的上限值。數字的上限值是大於等於該數字的最接近的整數。

  3. Mathf 數學運算
    Mathf.Abs絕對值
    計算並返回指定引數 f 絕對值。
    Mathf.Acos反餘弦
    static function Acos (f : float) : float
    以弧度為單位計算並返回引數 f 中指定的數字的反餘弦值。
    Mathf.Approximately近似
    static function Approximately (a : float, b: float) : bool
    比較兩個浮點數值,看它們是否非常接近,
    由於浮點數值不精確,不建議使用等於來比較它們。例如,1.0==10.0/10.0也許不會返回true。
    public class example : MonoBehaviour {
    publicvoid Awake() {
    if(Mathf.Approximately(1.0F, 10.0F / 10.0F))
    print("same");

    }
    }
    Mathf.Asin反正弦
    static function Asin (f : float) : float
    以弧度為單位計算並返回引數 f 中指定的數字的反正弦值。
    Mathf.Atan2反正切
    static function Atan2 (y : float, x :float) : float
    以弧度為單位計算並返回 y/x
    的反正切值。返回值表示相對直角三角形對角的角,其中 x 是臨邊邊長,而 y
    是對邊邊長。
    返回值是在x軸和一個二維向量開始於0個結束在(x,y)處之間的角。
    public class example : MonoBehaviour {
    publicTransform target;
    voidUpdate() {
    Vector3relative =
    transform.InverseTransformPoint(target.position);
    floatangle = Mathf.Atan2(relative.x, relative.z) *
    Mathf.Rad2Deg;
    transform.Rotate(0,angle, 0);
    }
    }
    Mathf.Atan反正切
    static functionAtan(f: float) :float
    計算並返回引數 f 中指定的數字的反正切值。返回值介於負二分之 pi 與正二分之 pi
    之間。
    Mathf.CeilToInt最小整數
    static function CeilToInt (f : float) : int
    返回最小的整數大於或等於f。
    Mathf.Ceil上限值
    static function Ceil (f : float) : float
    返回 f
    指定數字或表示式的上限值。數字的上限值是大於等於該數字的最接近的整數。
    Mathf.Clamp01限制0~1
    static function Clamp01 (value : float) :float
    限制value在0,1之間並返回value。如果value小於0,返回0。如果value大於1,返回1,否則返回value

    Mathf.Clamp限制
    static function Clamp (value : float, min :float, max : float) : float
    限制value的值在min和max之間, 如果value小於min,返回min。
    如果value大於max,返回max,否則返回value
    static function Clamp (value : int, min :int, max : int) : int
    限制value的值在min和max之間,並返回value。
    Mathf.ClosestPowerOfTwo最近的二次方
    static function ClosestPowerOfTwo (value :int) : int
    返回距離value最近的2的次方數。
    Mathf.Cos餘弦
    static function Cos (f : float) : float
    返回由引數 f 指定的角的餘弦值(介於 -1.0 與 1.0 之間的值)。
    Mathf.Deg2Rad度轉弧度
    static var Deg2Rad : float
    度到弧度的轉化常量。(只讀)
    這等於(PI * 2) / 360。
    Mathf.Mathf.Rad2Deg 弧度轉度
    static var Rad2Deg : float
    弧度到度的轉化常量。(只讀)
    這等於 360 / (PI * 2)。
    Mathf.DeltaAngle增量角
    static function DeltaAngle (current :float, target : float) : float
    計算給定的兩個角之間最短的差異。
    // Prints 90
    Debug.Log(Mathf.DeltaAngle(1080,90));
    Mathf.Epsilon小正數
    static var Epsilon : float
    一個很小的浮點數值。(只讀)
    最小的浮點值,不同於0。
    以下規則:

    - anyValue + Epsilon = anyValue
    - anyValue - Epsilon = anyValue
    - 0 + Epsilon = Epsilon
    - 0 - Epsilon = -Epsilon
    一個在任意數和Epsilon的之間值將導致在任意數發生截斷誤差。
    public class example : MonoBehaviour {
    boolisEqual(float a, float b) {
    if(a >= b - Mathf.Epsilon && a <= b +
    Mathf.Epsilon)
    returntrue;
    else
    returnfalse;
    }
    }
    Mathf.Exp指數
    static function Exp (power : float) : float
    返回 e 的 power 次方的值。
    Mathf.FloorToInt最大整數
    static function FloorToInt (f : float) :int
    返回最大的整數,小於或等於f。
    Mathf.Floor下限值
    static function Floor (f : float) : float
    返回引數 f
    中指定的數字或表示式的下限值。下限值是小於等於指定數字或表示式的最接近的整數。
    Mathf.Infinity正無窮
    static var Infinity : float
    表示正無窮,也就是無窮大,∞ (只讀)
    Mathf.InverseLerp反插值
    計算兩個值之間的Lerp引數。也就是value在from和to之間的比例值。
    //現在引數是3/5
    float parameter =Mathf.InverseLerp(walkSpeed, runSpeed, speed);
    Mathf.IsPowerOfTwo是否2的冪
    static function IsPowerOfTwo (value : int): bool
    如果該值是2的冪,返回true。
    // prints false
    Debug.Log(Mathf.IsPowerOfTwo(7));
    // prints true
    Debug.Log(Mathf.IsPowerOfTwo(32));
    Mathf.LerpAngle插值角度
    static function LerpAngle (a : float, b :float, t : float) : float
    和Lerp的原理一樣,當他們環繞360度確保插值正確。
    a和b是代表度數。
    public class example : MonoBehaviour {
    publicfloat minAngle = 0.0F;
    publicfloat maxAngle = 90.0F;
    voidUpdate() {
    floatangle = Mathf.LerpAngle(minAngle, maxAngle,
    Time.time);
    transform.eulerAngles= new Vector3(0, angle, 0);
    }
    }
    Mathf.Lerp插值
    static function Lerp (from : float, to :float, t : float) : float
    基於浮點數t返回a到b之間的插值,t限制在0~1之間。
    當t = 0返回from,當t = 1 返回to。當t = 0.5 返回from和to的平均值。
    Mathf.Log10基數10的對數
    static function Log10 (f : float) : float
    返回f的對數,基數為10。
    Mathf.Log對數
    static function Log (f : float, p : float): float
    返回引數 f 的對數。
    // logarithm of 6 in base 2
    //以2為底6的對數
    // prints 2.584963
    print(Mathf.Log(6, 2));
    Mathf.Max最大值
    static function Max (a : float, b : float): float
    static function Max (params values :float[]) : float
    返回兩個或更多值中最大的值。
    Mathf.Min最小值
    static function Min (a : float, b : float): float
    static function Min (params values :float[]) : float
    返回兩個或更多值中最小的值。
    Mathf.MoveTowardsAngle移動角
    static function MoveTowardsAngle (current :float, target : float, maxDelta :
    float) : float
    像MoveTowards,但是當它們環繞360度確保插值正確。
    變數current和target是作為度數。為優化原因,maxDelta負值的不被支援,可能引起振盪。從target角推開current,新增180度角代替。
    Mathf.MoveTowards移向
    static function MoveTowards (current :float, target : float, maxDelta :
    float) : float
    改變一個當前值向目標值靠近。
    這實際上和
    Mathf.Lerp相同,而是該函式將確保我們的速度不會超過maxDelta。maxDelta為負值將目標從推離。
    Mathf.NegativeInfinity負無窮
    static var NegativeInfinity : float
    表示負無窮,也就是無窮小,-∞(只讀)
    Mathf.NextPowerOfTwo下個2的冪
    Mathf.PingPong乒乓
    static function PingPong (t : float, length: float) : float
    0到length之間往返。t值永遠不會大於length的值,也永遠不會小於0。
    The returned value will move back and forthbetween 0 and length.
    返回值將在0和length之間來回移動。
    Mathf.PI圓周率
    static var PI : float
    PI(讀pai)的值,也就是圓周率(π)的值3.14159265358979323846...(只讀)
    Mathf.Pow次方
    static function Pow (f : float, p : float): float
    計算並返回 f 的 p 次方。
    Mathf.Repeat重複
    static function Repeat (t : float, length :float) : float
    迴圈數值t,0到length之間。t值永遠不會大於length的值,也永遠不會小於0。
    這是類似於模運算子,但可以使用浮點數。
    public class example : MonoBehaviour {
    voidUpdate() {
    transform.position= new
    Vector3(Mathf.Repeat(Time.time, 3),
    transform.position.y,transform.position.z);
    }
    }
    Mathf.RoundToInt四捨五入到整數
    static function RoundToInt (f : float) :int
    返回 f 指定的值四捨五入到最近的整數。
    如果數字末尾是.5,因此它是在兩個整數中間,不管是偶數或是奇數,將返回偶數。
    Mathf.Round四捨五入
    static function Round (f : float) : float
    返回浮點數 f 進行四捨五入最接近的整數。
    如果數字末尾是.5,因此它是在兩個整數中間,不管是偶數或是奇數,將返回偶數。
    Mathf.Sign符號
    static function Sign (f : float) : float
    返回 f 的符號。
    當 f 為正或為0返回1,為負返回-1。
    Mathf.Sin正弦
    static function Sin (f : float) : float
    計算並返回以弧度為單位指定的角 f 的正弦值。
    Mathf.SmoothDampAngle平滑阻尼角度
    static function SmoothDampAngle (current :float, target : float, ref
    currentVelocity : float, smoothTime : float,maxSpeed : float =
    Mathf.Infinity, deltaTime : float = Time.deltaTime) : float
    引數
    current
    當前的位置。
    target
    我們試圖達到的位置。
    currentVelocity
    當前速度,這個值在你訪問這個函式的時候會被隨時修改。
    smoothTime
    the target faster.
    要到達目標位置的近似時間,實際到達目標時要快一些。
    maxSpeed
    可選引數,允許你限制的最大速度。
    deltaTime
    上次呼叫該函式到現在的時間。預設為Time.deltaTime。
    隨著時間的推移逐漸改變一個給定的角度到期望的角度。
    這個值通過一些彈簧減震器類似的功能被平滑。這個函式可以用來平滑任何一種值,位置,顏色,標量。最常見的是平滑一個跟隨攝像機。
    //一個簡單的平滑跟隨攝像機
    //跟隨目標的朝向
    public class example : MonoBehaviour {
    publicTransform target;
    publicfloat smooth = 0.3F;
    publicfloat distance = 5.0F;
    privatefloat yVelocity = 0.0F;
    voidUpdate() {
    //從目前的y角度變換到目標y角度
    floatyAngle =
    Mathf.SmoothDampAngle(transform.eulerAngles.y, target.eulerAngles.y,ref
    yVelocity, smooth);
    //target的位置
    Vector3position = target.position;
    //然後,新角度之後的距離偏移
    position+= Quaternion.Euler(0, yAngle, 0) * new
    Vector3(0, 0, -distance);
    //應用位置
    transform.position= position;
    //看向目標
    transform.LookAt(target);
    }
    }

    Mathf.SmoothDamp平滑阻尼
    static function SmoothDamp (current :float, target : float, ref
    currentVelocity : float, smoothTime : float,maxSpeed : float =
    Mathf.Infinity, deltaTime : float = Time.deltaTime) : float
    引數
    current
    當前的位置。
    target
    我們試圖達到的位置。
    currentVelocity
    當前速度,這個值在你訪問這個函式的時候會被隨時修改。
    smoothTime
    要到達目標位置的近似時間,實際到達目標時要快一些。
    maxSpeed
    可選引數,允許你限制的最大速度。
    deltaTime
    上次呼叫該函式到現在的時間。預設為Time.deltaTime。
    描述
    隨著時間的推移逐漸改變一個值到期望值。
    這個值就像被一個不會崩潰的彈簧減振器一樣被平滑。這個函式可以用來平滑任何型別的值,位置,顏色,標量。
    public class example : MonoBehaviour{
    publicTransform target;
    publicfloat smoothTime = 0.3F;
    privatefloat yVelocity = 0.0F;
    voidUpdate()

{
floatnewPosition =
Mathf.SmoothDamp(transform.position.y, target.position.y, refyVelocity,
smoothTime);
transform.position= new
Vector3(transform.position.x, newPosition, transform.position.z);
}
}
Mathf.SmoothStep平滑插值
static function SmoothStep (from : float,to : float, t : float) : float
和lerp類似,在最小和最大值之間的插值,並在限制處漸入漸出。
public class example : MonoBehaviour{
publicfloat minimum = 10.0F;
publicfloat maximum = 20.0F;
voidUpdate()

{
transform.position= new
Vector3(Mathf.SmoothStep(minimum, maximum, Time.time), 0, 0);
}
}
Mathf.Sqrt平方根
static function Sqrt (f : float) : float
計算並返回 f 的平方根。
Mathf.Tan正切
static function Tan (f : float) : float
計算並返回以弧度為單位 f 指定角度的正切值。

  1. 【摘】

二五、補充:浮點型的不精確性

浮點數為什麼不精確?

其實這句話本身就不精確, 相對精確一點的說法是:
我們碼農在程式裡寫的10進位制小數,計算機內部無法用二進位制的小數來精確的表達。

什麼是二進位制的小數? 就是形如 101.11 數字,注意,這是二進位制的,數字只能是0和1。

101.11 就等於 1 * 2^2 +0 *2^1 + 1*2^0 + 1*2^-1 + 1*2^-2 =
4+0+1+1/2+1/4 = 5.75

下面的圖展示了一個二進位制小數的表達形式。

從圖中可以看到,對於二進位制小數,小數點右邊能表達的值是 1/2, 1/4, 1/8, 1/16,
1/32, 1/64, 1/128 … 1/(2^n)

現在問題來了, 計算機只能用這些個 1/(2^n) 之和來表達十進位制的小數。

我們來試一試如何表達十進位制的 0.2 吧。

0.01 = 1/4 = 0.25 ,太大

0.001 =1/8 = 0.125 , 又太小

0.0011 = 1/8 + 1/16 = 0.1875 , 逼近0.2了

0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了

0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 還是大

0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 這結果不錯

0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875
已經很逼近了, 就這樣吧。

這就是我說的用二進位制小數沒法精確表達10進位制小數的含義。

浮點數的計算機表示

那計算機內部具體是怎麼表示的呢?

計算機不可能提供無限的空間讓程式去儲存這些二進位制小數。

它需要規定長度, 在Java 中, 提供了兩種方式: float 和double ,
分別是32位和64位。

可以這樣檢視一下一個float的內部表示(以0.09f為例):
Float.floatToRawIntBits(0.09f)

你將會得到:1035489772, 這是10進位制的, 轉化成二進位制, 在前面加幾個0補足
32位就是:

0 01111011 01110000101000111101100

你可以看到它分成了3段:
第一段代表了符號(s) : 0 正數, 1 負數 , 其實更準確的表達是 (-1) ^0

第二段是階碼(e):01111011  ,對應的10進位制是 123

第三段是尾數(M)

你看到了尾數和階碼,就會明白這其實是所謂的科學計數法:
(-1)^s * M * 2^e

對於0.09f 的例子,就是:
0101110000101000111101100 * (2^123)
好像不對,這肯定遠遠大於0.09f !

這是因為浮點數遵循的是IEEE754 表示法, 我們剛才的s(符號) 是對的,但是 e(階碼)和
M(尾數)需要變換:

對於階碼e , 一共有8位, 這是個有符號數, 特別是按照IEEE754 規範,
如果不是0或者255, 那就需要減去一個叫偏置量的值,對於float 是127

所以 E = e - 127 = 123-127 = -4

對於尾數M ,如果階碼不是0或者255, 他其實隱藏了一個小數點左邊的一個 1
(節省空間,充分壓榨每一個bit啊)。
即 M = 1.01110000101000111101100

現在寫出來就是:
1.01110000101000111101100 * 2^-4
=0.000101110000101000111101100
= 1/16 + 1/64 + 1/128+ 1/256 + ….
= 0.0900000035762786865234375

你看這就是0.09的內部表示, 很明顯他比0.09更大一些, 是不精確的!

64位的雙精度浮點數double是也是類似的, 只是尾數和階碼更長, 能表達的範圍更大。
符號位 :1位
階碼 : 11位
尾數: 52位

上面的例子0.09f 其實是所謂的規格化的浮點數,
還有非規格化的浮點數,這裡就不展開了。

使用浮點數

由於浮點數表示的這種“不精確性”或者說是“近似性”, 對於精確度要求不高的運算還行,
如果我們用float或者double 來做哪些要求精確的運算(例如銀行)時就要小心了,
很可能得不到你想要的結果。

具體的改進方法推薦大家看看《Effective
Java》在第48條所推薦的“使用BigDecimal來做精確運算”。

有一個事實是浮點數不能精確的代表所有實數,同時浮點操作也不能精確的表示真正的算術運算,這導致了很多奇怪情形的產生。這種問題的產生與計算機通常代表的數字是有限精度
finite precision )有關。

比如,0.1和0.01(用二進位制儲存時)“不可表示”的意思是:當你試圖求解0.1的平方時,結果既不是0.01也不是一個接近於0.01的可表示的數。在系統為24位(單精度)表示時,0.1(十進位制)是被預先給定e
=−4,s
=110011001100110011001101(e是指數,s是有效數位。0.1是正數,符號位為0,
0.00011001100110011…=1.100110011001…X2^(-4),
所以其指數是-4),轉換為十進位制是0.100000001490116119384765625。平方之後結果為0.010000000298023226097399174250313080847263336181640625
但實際上最接近0.01的可表示的數是0.009999999776482582092285156250
同樣的,π或者π/2是不可表示的意味著當你嘗試計算tan(π/2)時不會得到一個有限的結果,或者結果甚至會導致溢位。因為π/2不能被精確的表示,所以對於標準的浮點硬體來說嘗試計算tan(π/2)是不可能的。

在C語言中的計算

/*足夠的數位來確保我們可以得到正確的近似值*/

double pi = 3.1415926535897932384626433832795;

double z = tan(pi/2.0);

最終得到的結果是16331239353195370.0。在型別為單精度時,運用tanf函式,結果會是−22877332.0。一樣的道理,當我們嘗試計算sin(π)時,也永遠不會得到0的結果。在使用雙精度時,結果會是(近似)
0.1225×10^-9,或者得到 −0.8742×10^−7的結果,當你採用單精度運算時。
雖然浮點數的加法和乘法是可交換的,比如a + b = b + a 以及a×b =
b×a,但這並不意味著它們是可以組合計算的,比如(a + b) + c 就不一定等於a + (b +
c)。
使用七個有效數位計算,例子:

a = 1234.567, b = 45.67834, c = 0.0004

而且,它們同樣不一定是可分配的,比如(a + b) ×c 也許和a×c +
b×c得到的結果並不一樣,例子如下

1234.567 × 3.333333 = 4115.223
1.234567 × 3.333333 = 4.115223
4115.223 + 4.115223 =4119.338
但是
1234.567 + 1.234567 = 1235.802
1235.802 × 3.333333 =4119.340

除了某些數學定律失去意義,沒有辦法確切的表示諸如π
和0.1這樣的數字之外,其他的一些小錯誤也會出現,比如:

約消:當兩個接近相等的運算元相減時也許會導致完全喪失精度。當我們讓兩個近似相等的數字做減法時,我們把最高有效數位設定為零,留下一個誤差大,不普遍的數。(When
we subtract two almost equal numbers we set the most significant digits to zero,
leaving ourselves with just the insignificant, and most erroneous,
digits.)。
比如,在確定一個函式的導數使用下列公式

從一個人的直覺出發,他會希望h是一個非常接近0的數字,但是,當我們使用浮點操作時,最小的數字並不能滿足導數最佳逼近的要求。隨著h變得越來越小,f
(a + h)
和f(a)之間的不同也就變得越來越小。把最普遍和誤差最小的數字丟棄掉,使得最大誤差的數字變得十分重要(cancelling
out the most significant and least erroneous digits and making the most
erroneous digits more
important)
。結果就是,一個本身是最小數字的h可能會比一個本身是更大數字的h求導得到的誤差更大。這或許是最常見但也最嚴重的精確度問題吧。

整數轉換並不直觀:把63.0/9.0轉換成整數最終得到7,但是把0.63/0.09轉換成整數或許會得到6。這是因為轉換通常是直接舍位擷取而不是四捨五入。floor(向下取整)和ceiling(向上取整)函式也許可以通過直觀的計算給我們答案。

有限的指數取值:結果可能上溢,無窮大或者產生下溢,得到一個比正常值要小的數字或者0。在這些情況下就會造成精度的損失。

測試除法正確性存在問題:檢查除數不為零並不能夠保證除法不會溢位

測試兩個數字是否相等存在問題:兩個在數學上相等的數列可能會產生不同的浮點數值。

0.1在計算機中不能被精確表示

#include<stdio.h>

#include<iostream>

int main()

{

double i;

/*

for (i=0; i != 10;i+=0.1)

{

printf("%.1lf\n",i);//這樣寫停不下來無限迴圈

}

*/

/*

for (i=0;i-10<0.00000001;i+=0.1)

{

printf("% .1lf\n",i);//這樣寫是可以停下來的。

//****因為記憶體中的小數是不穩定的,不能直接比較大小,只能是認為相減的差接近於0的時候是相等的

}

*/

for(i=0; (int) i!=10.0; i += 0.1) //double強轉int 之後小數點去掉i=10.000XXX...
X是可能出錯的位 (int)i=10 10就=10.0 就停下來了

{

printf("% .1lf\n",i);//這樣寫是可以停下來的。

}

system("pause");

return 0;

}

0.1 = 1/(2^4) + 1/(2^5) + 1/(2^8) + ...

其中0.1只能無限迴圈下去,這就意味著0.1在計算機中不能被精確表示。因為0.1無法用二進位制精確表示造成了錯誤,所以用0.25和0.24作為步長分別測試,發現果然0.25可以停止迴圈,而0.24不能停止迴圈。

二六、Unity中比較浮點數

  1. Mathf.Approximately近似

static function Approximately (a : float, b: float) : bool
比較兩個浮點數值,看它們是否非常接近,
由於浮點數值不精確,不建議使用等於來比較它們。

例如,1.0==10.0/10.0也許不會返回true。

public class example : MonoBehaviour {

public void Awake() { if(Mathf.Approximately(1.0F,10.0F/10.0F))
print("same"); } }

  1. 一個很小的浮點數值,但不同於0

規則:

- anyValue + Epsilon = anyValue

- anyValue - Epsilon = anyValue

- 0 + Epsilon = Epsilon

- 0 - Epsilon = -Epsilon

有點像Approximately的偏差值

bool isEqual(float a, float b) {

if(a >= b - Mathf.Epsilon && a <= b + Mathf.Epsilon)

return true;

else

return false;

}

二七、get/set的使用

  1. 一個變數可以用其來進行保護。當人為地對一個公共變數使用get/set進行封裝時,它就會受到保護,生出一個私有變數。該私有變數只能通過人為設定過的、以大寫字母開頭的get/set公共變可以在其他類中被使用。

  2. 一般情況下所有的變數均有get/set設定,只不過基本上這些都已經預設地被隱藏了。

  3. 當之設定get屬性時候是“只讀”狀態,只設置set屬性時是“只寫”狀態。

二八、一部動畫片的誕生

  1. 確立目標:簡單地以年齡群體來劃分面向物件;以最終的收穫效果來確定目標面向物件。

  2. 策劃:擬定粗略的預算;初步選定各類工作人員;試片的工作——製作費用預算和製作週期預算。當人為地對一個公共變數使用get/set進行封裝時,變可以在其他類中被使用。

  3. 前期創作:決定主創人員(原創漫畫人、作家或影視導演),一步一步精細化做出來。

  4. 指令碼分鏡圖:以圖畫展現劇情的第一步驟——用分鏡的方式表演故事,要註明光效和氣氛表現,要具有指導作用。指令碼師通常為一個人,否則容易出現風格不統一的情況。

  5. 設計稿:指令碼內每個鏡頭需要表達的所有東西需要繪畫在一張畫稿上,如角色的面部表情甚至一草一木,設計稿是一部動畫片繪製工作的根基,細緻的背景也都要呈現出來,每一個鏡頭均需要一個設計稿

  6. 原話、背景:手繪和電腦背景,手繪的話需要線拍。

  7. 作監修型:按照標準造型的規格監察所有原畫的形象並對其加以修改。

  8. 中間動畫:按照兩張畫稿繪製中間畫稿以求其動畫的流暢。

  9. 上色、合成。

  10. 剪接:與電影剪輯不同的是,剪接工作時檢定每個畫面的正確性並試圖加以修飾的事務。

  11. 配音。(通常高階製作會採用中期配音策略,以確定動畫片的導向)

  12. 音響效果的設計。

二九、劇本寫作

  1. 懸念(Suspense)、戲劇(Drama)、衝突(Conflict)是劇本三要素。

  2. 一份劇本要處處設定懸念以吸引觀眾,同時也推薦情理之中意料之外的結尾方式。

  3. 劇本中的臺詞應當儘可能
    貼合生活場景、儘可能地道、極可能符合人物身份和所處的環境氛圍,因此在劇本寫作的過程中往往臺詞需要精心撰稿,相對比較費力。

  4. 塑造人物形象時千萬不要拘泥於通過描述該人物的過去生活來表現,而是要儘可能通過該人物與周圍事務之間的反應與互動來體現。

  5. 在塑造人物時,比較討巧的方法是通過人物的(1)年齡;(2)工作;(3)人物關係;(4)社會資本/地位
    這四個方面來表現。【Let the characters’ interactions and obstacles dictate
    their personalities!】

  6. 將道理用事實呈現出來,將人物的內心感受通過人物即將所要採取的行動和事件表現出來,將形象用互動表達出來。

  7. 可以通過在便利貼上寫上一系列事件——某人做了某事,來對思路進行梳理。

  8. 要從結局開始思考,建議故事的開始和結局是相反的(情節需要有重大變化)。

  9. 可將整個劇情氛圍三個部分——起因(Build
    up)、經過(Adventure)、結果(Resolution)這三個部分中,起因和結果部分所佔比例遠遠要小於中間的“經過”部分,第一和三部分各佔四分之一,中間佔二分之一。

  10. 在第一部分和第二部分的結尾需要設定劇情上的轉折點。

  11. 通常建議在第一部分的三分之一出設定一個劇情上的轉折、第二部分上的中間處設定一個轉折點。於是整個故事就最少由四個轉折點構成,分別在——故事的起因部分三分之一出處、起因部分的結尾處、經過部分的中間處、經過部分的結尾處。

  12. 在故事在開端,第一個劇情轉折點出現之前,需要用十分平和得到方式來烘托氣氛,陰謀就在平靜之中醞釀,一切盡在平靜之中,以看似十分平淡的生活開場,這就是對比的表現。這時候讓一個人成為故事中心——這就是主人公,主人公不必每一場都要出現,但是他是故事的中心。

  13. 在表現主人公的蛇粉的時候一定要記住從上面的四個要點中考慮。描述主人公的事件要能夠為後續故事情節做好鋪墊,並讓主人公的世界觀對於觀眾清新可見。

  14. 主人公的設定要有優缺點,儘管可以讓主人公優點很多,但是也要設定相應的致命性的弱點以增加整個故事在情節上的懸念。

  15. 讓主人公即將做的每一個選擇都充滿這懸念,良好的設定懸念的方法類似於我能設想到的獨立遊戲《萬里同風》的完美版,把它的選項懸念觀移植到電影上就是——受眾在主人公進行選擇的時候對於多種選項將會導向的結局並不知情,主人公選擇各種選項的可能性也是未知的,但是主人公的選擇在他的自身條件/情理之中。比如,倘若主人公有一個優點,但是主人公得到優點並沒有在故事的開端就交代清楚而是在故事進行的過程中才被發現、在致命一擊時主人公正是通過這樣突如其來的“特殊優點”保全性命,那麼這樣的故事設定並不能充分說服觀眾。
    因此,儘可能將關於人物得到所有的性格特質在故事行進的開端部分就陳設到位,對於主人公的缺點也是一樣。

  16. 主人公得到剋星是懸念的開始。

  17. 主人公的絕技和軟肋在第一幕就要說清楚。避免讓絕技和軟肋的出現變成一種巧合。在後續的情節中,故事正是在主人公的絕技和軟肋上做文章。

  18. 故事的慾望通過為每一個小場景設定目標並讓這一系列目標成為人物的動機來達成。

  19. 人物的每一個目標都需要非常明確。

  20. 設定人物出場:劇情中所有重要人物的出現儘可能在第一部分(起因部分),次要人物出場可放在第二部分(經過部分)的前半部分,但是務必將所有與故事有關的人物在整部影片的前半部分就全部陳設和介紹完畢!

  21. 要通過主人公身邊的“襯托人物”來襯托出主人公的性格特質。比如,倘若主人公十分圓滑,那麼其襯托人物就很粗俗;倘若主人公十分小心謹慎,其襯托人物就是十分粗心大意,這就是巧妙的利用反襯來強化主人公的性格特質。同時,“襯托人物”需要不斷地問主人公“為什麼……”,“襯托人物”對主人公提出的問題要切合觀眾的心聲。因此,襯托人物就是引導主人公最像主人公的人物。

  22. 主人公的對手應當十分明確,這樣的對手可以是任何事物。對手也分小對手和大對手,小對手的作用僅限在某部分場景中,而大對手則是能夠在任何場景中隨心所欲地出現或是在後續情節中能夠更加嚴重地影響主人公的對手。

  23. 一頁紙代表著一分鐘所要講述的故事情節!【這是電影劇本的程式】

  24. 要懂得將主人公的生活常規打亂【主人公的生活不能像水一樣一成不變】:(1)事件需要是意料之外的;(2)事件是突然發生的;(3)事件是不容易被解決的、覆水難收。

  25. 在劇情的開端設定“Routine
    Killer”時,要讓主人公形成艱難抉擇,比如要不要接受任務?要表達清楚是什麼形成了主人公接受任務的重要阻礙,並且主人公只有先解決了那些障礙才能選擇接受任務,或是有了充分的理由才能夠接受任務。這些都具備了才能開啟第二幕。

  1. 在第二部分(經過部分,Adventure)開始時主人公應當充分明白自己的最終目標。

  2. 在情節上設定對比或者用另一種方法讓主人公遠離平靜的生活。

  3. 在撰寫第二部分的情節時,在中間點的兩端設定轉折增稠劑可以有效避免乏味。

  1. 第二部分的劇情也要有情節上得到跌宕起伏。

  1. 編劇要經常問自己“為什麼主人公在這裡不能直接選擇最簡單的處事策略?”、“為什麼主人公非要費經周折?”、“為什麼主人公在解決問題時總是笨手笨腳?”、“Why
    don’t they just……?”
    並且編劇必須要為這些問題給出合理的解釋,不要躲避這些問題或是將這些問題留給觀眾。諸如此類的問題對於劇本的健壯性很有意義,這些問題的合理解釋在現實生活中可能是很豐富多樣,但是在劇情中必須只能是一種合理的解釋,以便於人物形象鮮明化。

  2. 設定衝突時要使用對於主人公來說特別珍貴的事物。讓主角陷入絕境。

  1. 讓觀眾看到主人公的失敗和成功,以此來凸顯其結果的不可預料。

  2. 只有充滿矛盾衝突才能在第二場/第二部分的情節中不會冷場。當劇情變得平淡的時候就加些矛盾衝突和轉折進來。

  3. 在第三幕(第三部分——結果)開始之前主角在第二幕相信經過一番努力會有效果,但是正是他的努力加劇了整個事件的矛盾衝突並且讓事情變得更加糟糕,甚至讓觀眾誤以為主人公將會失敗告終。

  4. 讓主人公的成功變得豐滿一些。

  5. 讓觀眾在最後一幕的結場白中享受到一種所有磨難都已經歷盡的歡樂,以平和的方式展現或者總結一下整個影片,回到原先的平靜狀態再結束影片。

  6. 英文劇本寫作上的格式規範如下:

設定紙張長寬為8.5 * 11 英寸並按如下設定。

三十、Excel便捷實用指南

  1. 快速開啟常用文件:開啟Excel軟體後,點選左上角的“檔案”“選項”“高階”,在“啟動時開啟此目錄中的所有檔案”一欄中加入欲在開啟軟體時就自動快速開啟的文件地址即可。

  2. 插入表格sheet:直接點選下方sheet旁邊的“+”號,也可以shift+F11快捷插入新表。也可以在“檔案”“選項”“常規”中的“新建工作簿時包含的工作表數”中設定。

  3. 全選帶格式複製表格:單擊表格左上角的小三角形可以選中整個工作簿,以便於帶著原表的長寬格式進行復制。

  4. 深度隱藏工作簿中的某個表格:右擊該sheet,選擇“檢視程式碼”,在“屬性”欄裡面調整“Visible”為“VeryHidden”即可深度隱藏,別人無法簡單通過“取消隱藏”來檢視到工作簿中的這張表。屬性欄若沒有,則可以在“檢視”選項中調出,也可以直接快捷點選F4。

  5. Shift+Enter:結束編輯該單元格並將編輯區向上移動一個單位【因為直接Enter會向下移動一個單元格】。

  6. Alt+Enter:編輯過程中在單元格內換行。

  7. Ctrl+Enter:編輯完該單元格後選中該單元格。

  8. 自定義單元格格式:選中要自定義的單元格,設定單元格格式,選擇“自定義單元格格式”,並在自定義欄中加入需要自定義的內容即可,這樣就能夠少些一些重複的文字,讓這些重複的文字像會計專用符或百分符那樣自動填上去,具體程式碼是:“text”@“texttext”,其中text是需要自動補充上去的內容,@則表示使用者輸入的位置,此處的內容有使用者自己輸入。
    想要實現輸入某個值就能夠自定義地呈現其他某個特定的字元時,可以在程式碼欄裡輸入以下程式碼:[=1]”true”;[=0]”false”.
    這個程式碼表示——當輸入1的時候,自動將其變為true,輸入0的時候自動將其變為false。各個條件之間使用分好隔開,並且這種用法完全可以用在已經編輯好的單元格內容當中。
    想要實現手機號碼按照自己指定的方式呈現出來,如18801095301在輸入時或者已經輸入過後變成188-0109-5301,則在程式碼欄裡輸入000-0000-0000即可,0代表著數字佔位符。

  9. 凍結視窗:在檢視選項卡中選擇凍結窗格可以隨意凍結選中單元格左邊和上邊的行和列,可以有效將視窗進行凍結。也可以只凍結首行,這裡的首行指的是所選中單元格的首行,有時候我們所選中的單元格並不是所有表格的首行,因此,我們需要使用快捷命令Ctrl+上箭頭,將游標移動到相應的最首行單元格中再進行選擇。

  10. 資料的分級顯示:資料的分級顯示有利於使用者檢視和分析資料。在“資料”選項卡中選擇“分組”即可對所選中的航和列進行分級,類似於程式設計過程中的展開和隱藏,會有小的加號出現,這個加號就是分級功能的具體體現。點選“取消分組”即可取消所有的分組。

  11. 時間:利用Today()函式可以實現當前日期的顯示,這個時間是動態的,當明天開啟時候,他就會顯示明天的時間,這樣可以製作一些好玩的倒計時案例。Now()函式也是一樣。可以使用ctrl+分號直接填充當前的日期,當前的時間則是shift+ctrl+分號,這就能實現固定的時間顯示。

  12. 超長資料的記錄與儲存:錄入資料的時候,如果資料數值超過了15位,後面的所有位數都會變成0,這是因為Excel的精度大小是固定的,因此這就為身份證號等一些資訊的輸入帶來了隱性的災難,因此在輸入身份證號時應當將所選單元格(為方便起見,可以直接將所選單元格所在的列)設定為文字格式輸入,就能夠避免身份證號變成科學計數法的格式或者丟失身份證號後面的三位。也可以在每次輸入身份證號的之前輸入一個英文狀態下的撇號,避免這樣的事情的發生,使身份證號能夠精確而且有效地記錄和表示出來。輸入以0開的數字編號的時候也是同上的方法進行。

  13. 分數的輸入:輸入分數時,為避免一些分數變成日期格式,在輸入分數之前應當輸入一個零。

  14. 連續序列的輸入:連續的行號可以通過使用能夠返回行號的Row()函式來實現,也可以通過輸入一個序號之後按住ctrl下拉來實現,然而這兩種方法都是需要下拉才能進行填充序號,要是資料量非常龐大,就會十分麻煩。因此使用“開始”選項卡下右上方的“填充”功能,選擇對列填充並對於列的起始號和終止號進行設定即可完成快速填充序列。這裡可以設定相應的步長,就是相隔幾個序號進行填充。

  15. 利用陣列進行填充:在使用公式之後選擇需要批量進行數學運算的兩行或者行和列,並使用ctrl+shift+Enter即可運算對應的行乘以對應的列的結果。這就是利用陣列進行填充。

  16. 圖片背景變透明:插入圖片之後可以雙擊圖片進入“格式”面板並選擇“顏色”下拉選項,選擇“設定透明色”即可。

  17. 分列與合併單元格:在資料選項卡中,選擇“分列”選項即可對所選中的列內容進行分割並將分割的內容插入在新的列中,並且內部可以精細調整分隔符等內容;在合併單元格內容的時候,需要用到&對於單元格的內容進行連線,如果希望在單元格內容之間加入空格或者其他的一些符號,則通過雙引號括起即可,以加入空格為例,合併A1、A2、A3這三個單元格內容到一個單元格的時候,程式碼為:A1&””A2&””A3。如果後續需要將空格替換成“-”符號或者其他符號,不必再次
    輸入公式,只要選中該列並進行查詢替換即可,Excel也會識別對於空格符號的替換。

  18. 公式轉換為值:在所要轉換成值的單元格邊框處(待游標變成十字箭頭的時候)滑鼠右擊拖動該單元格假裝拖到任意一個地方並再回來,然後在彈出的選項當中選擇“僅複製數值”即可將公式轉換為值。也可以選中公式所在的單元格後再“開始”選項卡中選擇“貼上”“貼上數值”即可。

  19. 快速填充:通過Ctrl+G組合鍵,調出“定位條件”對話方塊,在對話方塊中選擇“空值”即可僅選中所選行或列中的空單元格,此時通過相對引用即可快速填充這些空單元格。

  20. 從外部匯入資料:從外部的文字文件中匯入資料可以使用“資料”選項卡中的“自文字”,並在彈出對話方塊中選擇相應的選項,將分割的方式進行設定並將所分割的列中單元格格式進行定義。從網站上匯入資料則只要選擇“從網站”即可,在彈出的對話方塊中輸入網址並在網站中找到所要匯入的資料區域,點選其左上角的“+”號即可匯入。匯入後的資料是動態更新的,如果想設定動態更新的頻率,可以選擇“資料”選項卡中的“屬性”選項並對於資料的屬性進行精確地設定,也可以直接點選“重新整理”進行重新整理操作。

  21. 資料保護:在“審閱”選項卡中選擇“保護工作表”,並可對其設定相應的密碼(也可忽略),這時,根據下方提示的“選定已被鎖定的單元格”和“選中未被鎖定的單元格”就表示滿足這兩個條件的單元格就會受到保護。如果在一張表中需要僅僅只對於某部分的單元格進行保護,則可以先選中使用者不需要進行保護的單元格,並按滑鼠右鍵設定其單元格格式,轉到“保護”,將預設勾選上的“保護”取消勾選,然後再到“審閱”選項卡中重複上述步驟即可對其進行保護。

  22. 設定批註:
    在“審閱”選項卡中可以對所選的單元格進行批註,批註的格式可以通過右擊滑鼠在彈出的選項卡中進行調節。插入一個形狀之後將其“編輯形狀”命令新增到快速訪問工具欄中,可以對於填充成為照片效果的批註的形狀進行編輯(一定要新增到快速訪問工具欄,否則直接在“插入”選項卡中選擇的話就沒有這個效果了)。此外,如果不想每次批註的時候在批註欄第一行出現使用者的名稱,可以在檔案下方的選項卡中選擇“常規”,並在底下找到“使用者名稱稱”,更改其使用者名稱稱即可。

Choose函式的使用:

  1. 不合並單元格居中顯示:在第一個單元格填入相應的文字內容之後,選擇要居中的單元格叢集並右鍵設定其單元格格式,將單元格格式中的“對齊”設定為“跨列居中”即可。

三一、C#中Read()後面使用Readline()時第一個Readline()失效原因及解決方法。

  1. 在C#中,從鍵盤上讀取一個鍵是使用Read()函式,這時候Read()函式讀取的是一個字元,注意,僅僅只是一個鍵的字元,多輸入的鍵將會以字元型別丟擲去。還需注意的是,在第一個字元鍵被讀取之後,其實C#內部還輸出了一個“換行”,這個換行會影響後面的Readline()從控制檯中讀取本應當由使用者想輸入的內容,效果是ReadLine()函式直接將上面遺留的除Read()已經取走的第一個字元之外的所有內容讀進來,造成使用者在使用ReadLine()時並不能夠正常輸入。

  2. 解決方法:這時我們只要寫一行程式碼將Read()函式讀取完第一個鍵(字元)後遺留並丟擲的其他內容統統忽略掉即可。這些內容可能是使用者輸入的超出一個鍵(字元)的部分,也可能是使用者只輸入一個鍵之後C#內部的一個“換行”。在Read()之後就寫一個ReadLine()就能夠將多餘的內容全部拋給ReadLine()。之後的第二個ReadLine()便會起作用了。

  3. 下方賦值C#方式詳述了本次結果的整個過程。

三二、C#中得到賦值及其轉換

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace ConvertionOfVariables

{

class Program

{

static void Main(string[] args)

{

System.Console.WriteLine("Way of variables convertion in C#.\n");

System.Console.WriteLine("Way 1: Please enter a number below: ");

string x = System.Console.ReadLine();

int y = Convert.ToInt32(x);

System.Console.Write("The number you have entered right now is: {0}", x);

System.Console.WriteLine(" --> It is the real number of the key you
entered.\n\n");

System.Console.WriteLine("Way 2: Please enter a number below: ");

int z = Convert.ToInt32(System.Console.ReadLine());

System.Console.Write("The number you have entered right now is: {0} ", z);

System.Console.WriteLine(" --> It is also the real number of the key you
entered.\n\n");

System.Console.WriteLine("Way 3: Please enter a number below: ");

int u = Convert.ToInt32(Console.Read());

System.Console.Write("The number you have entered right now is: {0} ", u);

System.Console.WriteLine(" --> It is the ASCII number of the key you
entered.\n\n");

System.Console.ReadLine(); // Try to read out al the content including the type
of a newline at C# back system.

System.Console.WriteLine("Way 4: Please enter a number below: ");

string v = System.Console.ReadLine();

int w = int.Parse(v);//Only when the input is integer can the PARSE function
convert the string to integer.

System.Console.Write("The number you have entered right now is: {0} ", w);

System.Console.WriteLine(" --> It is the real number of the key you
entered.\n\n");

System.Console.ReadKey();

}

/// <summary>

/// Question of why the input is banned after 'Read' function

/// </summary>

public void QuestionLeftBehind()

{

int x = System.Console.Read();

System.Console.WriteLine(x);

string y = System.Console.ReadLine();

System.Console.WriteLine(y);

}

public void AnswerAndSolFrQs()

{

int a = System.Console.Read();

System.Console.ReadLine();// Solution!!!!! ---> This Readline() function can
read out the type of newline in C# back system.

string b = System.Console.ReadLine();

System.Console.WriteLine(b);

System.Console.ReadKey();

//Answer: The value of y is the remained letters except the first letter input
from the keyboard,

//If you don't put anything but only input a key for the first Read() function,
the key you entered actually contains two characters.

//The first is the ASCII number of the key you entered while the other is the
type of a newline set at the back of the C# system!!!

//So you never get the access to input y unless you receive the type of
'newline' and ignores it.

//Solution: Place ''System.Console.Readline()'' to output the type of that
newline!

}

}

}

三三、XML顏色表示

  1. Android:background:”#000000”
    兩個兩個看#號後面的數字,分別表示RGB三種顏色所佔比例的十六進位制表示。比如通紅為“#FF0000”,其中的F就是十六進位制下的顏色表示方式。可以在Photoshop的顏色填充面板裡面也能夠找到相應的值。

三四、Animate基礎用法

  1. 標尺:Ctrl + Shift +Alt + R。

  2. 重做:Ctrl + Y。

  3. 拆分文字:Ctrl +
    B。【拆分漢字,將一個句子以文字為單位進行拆分。或是可以理解為“打散”功能,可以直接將一個物件進行打散,打散之後可以按F8重組成為一個元件。】

  4. 每個圖層都能夠被鎖住。

  5. 通過修改形狀新增形狀提示,可以將補間過程中移動的點固定住,固定時兩個提示分別進行調整。可以用快捷鍵Ctrl

    • Shift + H。【注意,字母之間的轉換應當先將字母通過Ctrl +
      B鍵轉換為分散形狀,再選擇建立形狀補間!】
  6. 建立傳統補間動畫之後對其補間可以進行優化。對應的優化選項如下圖:

  1. 得到

三五、計算機圖形學

  1. 想要表示更多的顏色,可以使用查詢表的方式,將查詢表中的顏色作為要使用的最終物件,通過索引的方式使用,索引記錄的是它們的地址,這就有效地增加了短暫的位數能夠表示的顏色數。

  2. 如果解析度為640*480,幀快取大小為1MB,則可以顯示多少中顏色?
    答:[(640*480)*X] = (1024KB*1024B*8bit),X向下取整。

  3. 隔行掃描可以有效提高成像質量,降低螢幕閃爍。原來1/30的非隔行掃描改成隔行掃描之後可以變成1/60,也就是重新整理速率每秒接近60幀。儘可能在不增加硬體成本的前提下可以提高成像質量。

  4. 傳統的提高解析度的方法有插值方法,現代較為新穎的方式是超解析度(SR)方法。

  5. GPU採用流式運算,具有並行的特徵,可以大大提高圖形運算效率。

  6. 圖形系統的組成:
    (1)硬體:輸入裝置和顯示裝置;
    (2)軟體:圖形軟體包。

  7. 取樣模式:直接把測量資料返回給程式,不需要觸發器;【缺點:程式每次都需要不停地去問】。

  8. 請求模式:僅當用戶觸發裝置時才向程式返回測量資訊——鍵盤命令列輸入;【只有裝置在被使用的時候它才會返回資訊,對資源的消耗比上面的“取樣模式”好了很多。當只有一個裝置的時候會有較高的效率,但是當有多個裝置輸入的時候它無法併發處理】採用優先順序的方式時,會有當事裝置之外的那些裝置輸入的資訊已經被徹底丟失。

  9. 事件模式:系統可能有多個輸入裝置,每個裝置可以在任意時間被使用者觸發,每個裝置觸發時會產生一個時間,裝置的測量資料放入一個佇列中,這個佇列就是事件佇列,由應用程式去決定如何處理。{拓展:事件模式也無法併發處理,而是採用佇列的形式去響應,只不過這樣的佇列響應方式是十分迅速的。}

  10. {拓展:實際上,計算機裡面沒有絕對的併發,只不過作業系統在處理多件事情的時候通過交替的形式偽造了併發的效果,這種交替的時間間隔是時分渺小的,以至於令人無法察覺到兩個事件之間實際上是有交替性的。}

  11. 回撥函式:使用者對每一種圖形系統識別的時間定義一個對應的回撥函式。當事件發生時,根據事件型別執行相應的回撥函式。

  12. {拓展:模仿人類的視覺系統->利用三原色是因為人的眼睛有三種視錐細胞,只有三種顏色感應傳給大腦,視錐細胞支隊顏色敏感;視杆細胞則對於亮度敏感(實際上眼睛對於亮度是更加敏感的,所以有人類似於RGB地去提出YUV),它是隻識別單色的。}

  13. Process the Vertex:
    頂點處理,主要有座標系變換(等價於矩陣變換)——依次進行轉換,物件座標、觀察者座標、螢幕座標;頂點模組還計算計算頂點顏色。

  14. 光柵化是將裁剪後的圖元轉化為甄嬛岑中的畫素的過程。光柵化為每個圖元輸出一組片元(Fragment),片元是潛在的畫素,裡面記錄有幀快取中的位置、顏色和深度屬性。深度資訊對於三維計算機圖形學非常有用——Z
    Buffer,表示距離的遠近。

  15. 片元處理決定幀快取中對應畫素的顏色。

  16. 在頂點處理的時候就可以對顏色進行處理。

  17. 綜上可以看出,顏色表示有兩種方式——頂點處設定和片元處設定。

在頂點處設定的時候只需要設定好頂點處的顏色屬性即可,而使用片元的話則需要每一個畫素都要涉及到顏色屬性的設定。由此可以看出在片元中的顏色表示(Fragment
shader)會更加的精細,但是也會更為消耗記憶體。頂點著色(Vertex
shader)設定的時候則會近似的將頂點內部的區域著色為同一種或近似的顏色中,顏色精度就會弱一些。

  1. 顏色由紋理對映或者頂點顏色插值獲得。插值就是在兩個已知的點中間近似獲取一個點的過程——利用已有的資料去推測未知的資料。

  2. 片元處理中還需要對隱藏面進行消除——利用消隱演算法。

  3. 消隱
    真實感圖形繪製過程中,由於投影變換失去了深度資訊,往往導致圖形的二義性。要消除這類二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱之為消除隱藏線和隱藏面,或簡稱為消隱,經過消隱得到的投影圖稱為物體的真實圖形。

  4. 為什麼需要紋理對映:紋理對映是真實感圖形研究中必不可少的技術;圖形繪製的方式繪製圖形會有很高的精度,但是他的代價很高,因為它需要逐個畫素逐個畫素進行處理,然而使用紋理對映的方式繪製圖形時,精度能夠達到前者80%的精度,但其代價上卻可能只是前者的10%左右。由此可以發現紋理對映的最終目的是——用盡可能小的代價去繪製圖形。

(1)比如繪製一面磚牆,就可以使用一幅具有真實感的影象或者照片作為紋理貼到一個矩形上,這樣,一面逼真的磚牆就畫好了。如果不用紋理對映的方法,這牆上的每一塊磚都要作為一個獨立的多邊形來繪製。另外,紋理對映能夠保證在變換多邊形時,多邊形上的紋理也會隨之變化。例如,用透視投影模式觀察牆面時,離視點遠的牆壁的磚塊的尺寸就會縮小,而離視點近的就會大些,這些是符合視覺規律的。

(2)紋理對映是真實感影象製作的一個重要部分,運用它可以方便的製作出極具真實感的圖形而不必花過多時間來考慮物體的表面細節。

(3)缺點:然而紋理載入的過程可能會影響程式執行速度,當紋理影象非常大時,這種情況尤為明顯。如何妥善的管理紋理,減少不必要的開銷,是系統優化時必須考慮的一個問題。還好,OpenGL提供了紋理物件物件管理技術來解決上述問題。與顯示列表一樣,紋理物件通過一個單獨的數字來標識。這允許OpenGL硬體能夠在記憶體中儲存多個紋理,而不是每次使用的時候再載入它們,從而減少了運算量,提高了速度。

(4)OpenGL對紋理物件的管理和應用具體步驟如下:

第一步:定義紋理物件

const int TexNumber4;

GLuint mes_Texture[TexNumber]; //定義紋理物件陣列

第二步:生成紋理物件陣列

glGenTextures(TexNumber,m_Texture);

第三步:通過使用glBindTexture選擇紋理物件,來完成該紋理物件的定義。

glBindTexture(GL_TEXTURE 2D,m_Texture[0]);

glTexImage2D(GL_TEXTURE_2D,0,3,mes_Texmapl.GetWidth(),mee_Texmapl.GetHeight(),0,GL_BGR_EXT,GL_UNSIGNED_BYTE,mse_Texmapl.GetDibBitsl'trQ);

第四步:在繪製景物之前通過glBindTexture,為該景物載入相應的紋理。

glBindTexture(GLes_TEXTURE_2D,mse_Texture[0]);

第五步:在程式結束之前呼叫glDeleteTextures刪除紋理物件。

glDeleteTextures(TexNumber, mee_Texture);

這樣就完成了全部紋理物件的管理和使用。

  1. 光照型別:單點光源與多點光源。

  2. 多邊形(表示便捷可以用封閉折線並且內部有明確定義的物件)呈現中,三條性質確保多邊形可以被正確地顯示出來:

(1)簡單——邊不交叉;

(2)凸——多邊形內任意兩點連線仍然在多邊形內;

(3)平面——所有頂點在同一平面內。

  1. 為什麼使用三角形繪製圖形?

答:一些典型繪製演算法只對平面凸多邊形才能正確繪製;校驗簡單性和凸性代價太高;三角形總能夠滿足以上條件並且繪製更快;需要演算法將任意多邊形剖分為三角形。

  1. 三角剖分的過程:

(1)細長三角形繪製效果不逼真,因此應當儘量避免使用細長的三角形繪製。也就是對細長的三角形進行剖分。

(2)相對來說,相同大小的三角形繪製效果更好。也就是應當儘可能產生相同大小的三角形。

(3)剖分時要最大化原先的最小角。

(4)使用遞迴的方式進行剖分。——缺點:凹多邊形剖分會出錯;解決方法——新增限制,比如,從最左邊的頂點開始分割。

  1. 為什麼 要做圖形變換?

答:在圖形繪製時會遇到不同的表達空間,統一不同的圖形空間,正式利用圖形變換的方式來進行統一。不同座標系下一致地表達圖形。

  1. 為什麼要引入齊次座標?——點跟向量容易混淆;齊次座標可以將在整個變換型別轉換成齊次座標表示地形式,獲得形式上的統一。【為了統一表示圖形變換,引入齊次座標和變換矩陣的概念,圖形變換通過矩陣的乘法來實現。】

  2. 剛體變換:不改變形和提及只改百年位置和方向。

  3. 平移指的是將物體沿直線路徑從一個座標位置移動到另一個座標位置的重定位。

  4. 繞著任意基準點的旋轉變換步驟:(1)先將座標原點平移到目標物體的位置並將兩個座標軸重合;(2)根據要求對目標物體進行變換,比如旋轉、縮放等;(3)將座標原點平移回座標位置,逆平移過程。【考點】

  5. 仿射變換(Affine
    Transform):每一個變換後的座標都是原座標的線性函式。特性:平行線變換到平行線且有限點變化到有限點。平移旋轉縮放反射和錯且都是仿射變換的特例。

  6. 旋轉座標轉換的矩陣推導

兩角和(差)公式

旋轉變換一般是按照某個圓心點,以一定半徑r旋轉一定的角度α。假定點A(x,y)想經過旋轉變換到達B(x',y'),已知旋轉角度α和點A座標,計算出點B。

要計算點B則分別計算他的x'和y'分量

得出結果:

根據矩陣乘法計算規則,可以推出

【在三維座標系下可視為繞Z軸旋轉的變換矩陣】

**只要給出旋轉角度,就能計算出矩陣,然後就可以用這個矩陣分別左乘每一個點,就能計算出這個點旋轉後的點座標
這樣我們就可以通過矩陣變換座標了。**

  1. 旋轉變換的變換矩陣:

  1. 三維變換矩陣的功能分塊

  1. 三維座標變換包括幾何變換和座標變換【計算座標變換】:

  1. 逆矩陣與轉置矩陣的關係:
    一、兩者的含義不同:

(1)矩陣轉置的含義:將A的所有元素繞著一條從第1行第1列元素出發的右下方45度的射線作鏡面反轉,即得到A的轉置。一個矩陣M,
把它的第一行變成第一列,第二行變成第二列等,最末一行變為最末一列,
從而得到一個新的矩陣N。 這一過程稱為矩陣的轉置。即矩陣A的行和列對應互換。

(2)逆矩陣的含義:一個n階方陣A稱為可逆的,或非奇異的,如果存在一個n階方陣B,使得AB=BA=E,則稱B是A的一個逆矩陣。A的逆矩陣記作A-1。

2、兩者的基本性質不同:

(1)矩陣轉置的基本性質:(A±B)T=A=±BT;(A×B)T= BT×AT;(AT)T=A;(KA)T=KA。

(2)逆矩陣的基本性質:可逆矩陣一定是方陣。如果矩陣A是可逆的,其逆矩陣是唯一的。A的逆矩陣的逆矩陣還是A。記作(A-1)-1=A。可逆矩陣A的轉置矩陣AT也可逆,並且(AT)-1
=(A-1)T (轉置的逆等於逆的轉置)。

二、矩陣的轉置和逆矩陣之間的聯絡:矩陣的轉置和逆矩陣是兩個完全不同的概念。轉置是行變成列列變成行,沒有本質的變換,逆矩陣是和矩陣的轉置相乘以後成為單位矩陣的矩陣。

  1. 通過Word輸入多行多列矩陣的方法

  2. 首先輸入一個矩陣,然後設定成線性。

    https://pic4.zhimg.com/80/e1358316f7df0fbfa308d66045a01e34_hd.jpg

  3. 這時就會發現,公式框裡的東西變成了:[■(1&2&3@4&5&6@7&8&9)]。

  4. 最外面的方括號,其實就是把矩陣括起來的方括號。實心矩形後面的圓括號裡面就是矩陣的內容,按行書寫,用“&”分列,用“@”分行。於是,我們就可以修改這個式子,來增加行列。

  5. 比如修改成[■(&&&&&@&&&&&@&&&&&)]然後把公式轉換成專業型,就會變成

也就是一個3行6列的空矩陣,這時候就可以往裡面填坑了。

三六、資料結構與演算法引子

  1. 資料結構: 研究資料元素之間的關係。

  2. 時間複雜度: 執行一段程式所花的時間開銷。【記錄一段程式碼執行/運算幾次】

  3. 空間複雜度:
    執行一段程式所花的空間開銷。【不計算直接通過CPU進行運算的部分,只計入那些分配空間時的程式碼部分並且不重複記錄運算過程中的重分配。】

  4. 用空間換時間的典型案例:

#include <iostream>

using namespace std;

void search(int a[], int len);

int main(){

int array[] = {0,1,2,3,4,5,6,7,8,7,6,5,34,546,467,53,

546,456,456,6,56,456,54,6,54,6,546,546,54,6,25,

345,423,5,34,5};

//注意陣列不要太長否則分配空間將會出現巨大損耗而導致空間不足

search(array, sizeof(array)/sizeof(*array));

//將陣列的第一個元素的位置和陣列長度傳入函式中

}

void search(int a[], int len){

int sp[1000] = {0}; //宣告並初始化新建的一個索引陣列

int max = 0;

for (int i = 0; i < len; i++){

int index = a[i] -1; //開闢索引存放空間並將地址按順序分配到其中

(就在這時其實就已排好序)

sp[index]++; //記錄陣列中每個資料元素出現的個數

}

for (int i = 0; i < 1000; i++){

if(max < sp[i]){

max = sp[i]; //比較記錄下的次數大小並記錄下次數最大的

}

}

for (int i = 0; i < 1000; i++){

if (max == sp[i]){

cout << i+1 ; //通過索引找到原來資料的內容

}

}

}

三七、指向結構體變數的指標

前面我們通過“結構體變數名.成員名”的方式引用結構體變數中的成員,除了這種方法之外還可以使用指標。

前面講過,&student1 表示結構體變數 student1 的首地址,即 student1
第一個項的地址。如果定義一個指標變數 p 指向這個地址的話,p 就可以指向結構體變數
student1 中的任意一個成員。

那麼,這個指標變數定義成什麼型別呢?只能定義成結構體型別,且指向什麼結構體型別的結構體變數,就要定義成什麼樣的結構體型別。比如指向
struct STUDENT 型別的結構體變數,那麼指標變數就一定要定義成 struct STUDENT*
型別。

下面將前面的程式用指標的方式修改一下:

# include <stdio.h>

# include <string.h>

struct AGE

{

int year;

int month;

int day;

};

struct STUDENT

{

char name[20]; //姓名

int num; //學號

struct AGE birthday; //生日

float score; //分數

};

int main(void)

{

struct STUDENT student1; /*用struct
STUDENT結構體型別定義結構體變數student1*/

struct STUDENT *p = NULL; /*定義一個指向struct
STUDENT結構體型別的指標變數p*/

p = &student1; /*p指向結構體變數student1的首地址, 即第一個成員的地址*/

strcpy((*p).name, "小明"); //(*p).name等價於student1.name

(*p).birthday.year = 1989;

(*p).birthday.month = 3;

(*p).birthday.day = 29;

(*p).num = 1207041;

(*p).score = 100;

printf("name : %s\n", (*p).name); //(*p).name不能寫成p

printf("birthday : %d-%d-%d\n", (*p).birthday.year, (*p).birthday.month,
(*p).birthday.day);

printf("num : %d\n", (*p).num);

printf("score : %.1f\n", (*p).score);

return 0;

}

輸出結果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

我們看到,用指標引用結構體變數成員的方式是:

(*指標變數名).成員名

注意,*p
兩邊的括號不可省略,因為成員運算子“.”的優先順序高於指標運算子“*”,所以如果 *p
兩邊的括號省略的話,那麼 *p.num 就等價於 *(p.num) 了。

從該程式也可以看出:因為指標變數 p 指向的是結構體變數 student1
第一個成員的地址,即字元陣列 name 的首地址,所以 p 和 (*p).name 是等價的。

但是,“等價”僅僅是說它們表示的是同一個記憶體單元的地址,但它們的型別是不同的。指標變數
p 是 struct STUDENT* 型的,而 (*p).name 是 char* 型的。所以在 strcpy 中不能將
(*p).name 改成 p。用 %s 進行輸入或輸出時,輸入引數或輸出引數也只能寫成
(*p).name 而不能寫成 p。

同樣,雖然 &student1 和 student1.name
表示的是同一個記憶體單元的地址,但它們的型別是不同的。&student1 是 struct
STUDENT* 型的,而 student1.name 是 char* 型的,所以在對 p
進行初始化時,“p=&student1;”不能寫成“p=student1.name”。因為 p 是 struct
STUDENT* 型的,所以不能將 char* 型的 student1.name 賦給 p。

此外為了使用的方便和直觀,用指標引用結構體變數成員的方式:

(*指標變數名).成員名

可以直接用:

指標變數名->成員名

來代替,它們是等價的。“->”是“指向結構體成員運算子”,它的優先順序同結構體成員運算子“.”一樣高。p->num
的含義是:指標變數 p 所指向的結構體變數中的 num 成員。p->num 最終代表的就是 num
這個成員中的內容。

下面再將程式用“->”修改一下:

# include <stdio.h>

# include <string.h>

struct AGE

{

int year;

int month;

int day;

};

struct STUDENT

{

char name[20]; //姓名

int num; //學號

struct AGE birthday; /*用struct AGE結構體型別定義結構體變數birthday,
生日*/

float score; //分數

};

int main(void)

{

struct STUDENT student1; /*用struct
STUDENT結構體型別定義結構體變數student1*/

struct STUDENT *p = NULL; /*定義struct STUDENT結構體型別的指標變數p*/

p = &student1; /*p指向結構體變數student1的首地址, 即第一項的地址*/

strcpy(p->name, "小明");

p->birthday.year = 1989;

p->birthday.month = 3;

p->birthday.day = 29;

p->num = 1207041;

p->score = 100;

printf("name : %s\n", p->name); //p->name不能寫成p

printf("birthday : %d-%d-%d\n", p->birthday.year, p->birthday.month,
p->birthday.day);

printf("num : %d\n", p->num);

printf("score : %.1f\n", p->score);

return 0;

}

輸出結果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

但是要注意的是,只有“指標變數名”後面才能加“->”,千萬不要在成員名如 birthday
後面加“->”。
綜上所述,以下 3 種形式是等價的:

結構體變數.成員名。

(*指標變數).成員名。

指標變數->成員名。

其中第 3
種方式很重要,通常都是使用這種方式,另外兩種方式用得不多。後面講連結串列的時候用的也都是第
3 種方式。

三八、指向結構體陣列的指標

在前面講數值型陣列的時候可以將陣列名賦給一個指標變數,從而使該指標變數指向陣列的首地址,然後用指標訪問陣列的元素。結構體陣列也是陣列,所以同樣可以這麼做。

我們知道,結構體陣列的每一個元素都是一個結構體變數。如果定義一個結構體指標變數並把結構體陣列的陣列名賦給這個指標變數的話,就意味著將結構體陣列的第一個元素,即第一個結構體變數的地址,也即第一個結構變數中的第一個成員的地址賦給了這個指標變數。比如:

# include <stdio.h>

struct STU

{

char name[20];

int age;

char sex;

char num[20];

};

int main(void)

{

struct STU stu[5] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M',
"Z1207035"}, {"小七", 23, 'F', "Z1207022"}};

struct STU *p = stu;

return 0;

}

此時指標變數 p 就指向了結構體陣列的第一個元素,即指向
stu[0]。我們知道,當一個指標指向一個數組後,指標就可以通過移動的方式指向陣列的其他元素。

這個原則對結構體陣列和結構體指標同樣適用,所以 p+1 就指向 stu[1] 的首地址;p+2
就指向 stu[2] 的首地址……所以只要利用 for
迴圈,指標就能一個個地指向結構體陣列元素。

同樣需要注意的是,要將一個結構體陣列名賦給一個結構體指標變數,那麼它們的結構體型別必須相同。

下面編寫一個程式:

# include <stdio.h>

struct STU

{

char name[20];

int age;

char sex;

char num[20];

};

int main(void)

{

struct STU stu[3] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M',
"Z1207035"}, {"小七", 23, 'F', "Z1207022"}};

struct STU *p = stu;

for (; p<stu+3; ++p)

{

printf("name:%s; age:%d; sex:%c; num:%s\n", p->name, p->age, p->sex,
p->num);

}

return 0;

}

輸出結果是:
name:小紅; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022

此外同前面“普通陣列和指標的關係”一樣,當指標變數 p 指向 stu[0] 時,p[0] 就等價於
stu[0];p[1] 就等價於 stu[1];p[2] 就等價於 stu[2]……所以 stu[0].num 就可以寫成
p[0].num,其他同理。下面將上面的程式用 p[i] 的方式修改一下:

# include <stdio.h>

struct STU

{

char name[20];

int age;

char sex;

char num[20];

};

int main(void)

{

struct STU stu[3] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M',
"Z1207035"}, {"小七", 23, 'F', "Z1207022"}};

struct STU *p = stu;

int i = 0;

for (; i<3; ++i)

{

printf("name:%s; age:%d; sex:%c; num:%s\n", p[i].name, p[i].age, p[i].sex,
p[i].num);

}

return 0;

}

輸出結果是:
name:小紅; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022

實際上,(*p).node可以寫成p->node,表示指向結構體的指標。

三九、C++實現“按任意鍵繼續”

  1. 直接呼叫系統函式 system("pause");

例如:

#include<iostream>

using namespace std;

int main()

{

system("pause");

return 0;

}

  1. 呼叫getch()函式:需要include<conio.h>

例如:

#include<conio.h>

int main()

{

prinf("按任意鍵繼續\n");

getch();

return 0;

}

  1. 呼叫getchar()函式:需要include<stdio.h>

例如:

#include<stdio.h>

int main()

{

prinf("按 Enter 鍵繼續\n");

getchar();

return 0;

}

  1. 使用cin的get()函式

例如:

#include<iostream>

using namespace std;

int main()

{

cout<<"按 Enter 鍵繼續"<<endl;

cin.get();

return 0;

}

注意:只有前兩種可以真正實現“按任意鍵繼續”,後兩種必需按下Enter 鍵才行。

  1. 利用cin.good()和cin.fail()判斷:cin.good()為true時,輸入的資料型別與定義的資料型別一致;
    cin.fail()為true時,輸入的資料型別與定義的不符。可以直接將其作為if語句或者while語句當中的判斷條件進行使用。

  2. 使用cin.clear();可以清除錯誤標記,重新開啟輸入流,但是輸入流中依舊保留著之前的不匹配的型別,而使用cin.sync();則可以清除cin快取區的資料。

四十、ends、endl和flush區別

輸出語句後方使用ends表示重新整理緩衝區後加一個空格;使用endl表示重新整理緩衝區後加一行;使用flush表示重新整理緩衝區後什麼也不做,僅僅只是重新整理緩衝區而已。

那麼為什麼要重新整理緩衝區呢?因為輸入的資料會快取到記憶體中,待所有資料輸出呈現到終端時將會花費一定時間,因此,當輸出時進行緩衝區重新整理工作有效地提高了快取區輸出緩慢的問題。

四一、C++對異常錯誤輸入的處理問題

在使用c++ 標準輸入std::cin
中,我們通常會按照正確的型別資料輸入,但是,如果我們不按照正確的型別資料輸入,結果如何呢?

現在我們使用下面的簡單c++程式碼,來測試一下:

#include<iostream>

int mina(void)

{

int number;

//第一次輸入

std::cout<<"Please Enter a number\n";

std::cin>>number;

//第二次輸入

std::cout<<"Please Enter again \n";

std::cin>>number;

return 0;

}

執行程式後輸出我們之前程式碼的中的提示“please enter a number
,等待輸入;當我沒有像提示那樣輸入數字,輸入了一個字母,當在鍵入字母后,按”enter”
後出現的結果,不在接受其餘的輸入;

很明顯我們發現了缺陷,那就是當我們輸入錯的資料的情況下,類似這樣的程式會崩潰掉,那我們應該怎麼做呢,當然創造c++標準庫的這些牛人也考慮這個缺陷,他們設計出了重新整理輸入輸出流的一些辦法,類似在應對輸入錯誤時
,使用std::cin.clear();這就是將錯誤的輸入緩衝給清除掉。

#include<iostream>

int main(void)

{

int number;

std::cout<<"Please enter a number"<<std::endl;

std::cin>>number;

if(!std::cin)

{

std::cin.clear();

}

std::cout<<"Please Enter Again"<<std::endl;

std:: cin>>number;

std::cout<<"just now ,your enter is "<<number<<std::endl;

return 0;

}

和上面的操作一樣我們根據提示輸入錯誤資料;在鍵入”enter“出現了這樣的結果

似乎沒按照我們預想的那樣的進行,還是沒法再進行其他輸入。

為什麼呢?

我們來推斷一下,當我們通常正確輸入時,當輸入資料結束後,按”enter”,會有換行操作!

但是,這裡,在輸入錯誤時,時沒有的, 那有可能,在錯誤輸入時
,後面的操作的就被終止了。

當我們輸如錯誤後,鍵入”enter” 就直接出現了上面的結果。我,猜測,會不會是“enter
“在做怪。

然後我們來嘗試改善程式碼

#include<iostream>

int main(void)

{

int number;

std::cout<<"Please enter a number"<<std::endl;

std::cin>>number;

if(!std::cin)

{

std::cin.clear();

std::cin.get(); // 獲取輸入的enter鍵;也可以使用getchar();

}

std::cout<<"Please Enter Again"<<std::endl;

std:: cin>>number;

std::cout<<"just now ,your enter is "<<number<<std::endl;

return 0;

}

執行,出現提示,等待輸入; 輸入錯誤資料並鍵入“enter”;
可以再次進行輸入了!從上面的猜測,我們發現,其實我們的猜測是對的。因為沒有專門接收“enter”,所以,輸入沒有被重新整理。

以上是用來實驗的簡單程式,僅僅簡單用於驗證。當然這些程式的驗證只針對使用標準輸入函式,當然如果通過自己編寫的輸入函式,肯定不適用。

通過簡單的程式,我們發現了應對錯誤輸入的有效方法。同時,我們可以根據以上的進行更深層次的改良。以便我們寫出更優秀的程式

因為我們沒有詳細瞭解標準輸入輸出庫函式內部,所以會經常出現類似的問題,但是當我們經常使用,便會發現其中的一些需要注意的地方。

案例:

# include <iostream>

# include <cstdlib>

using namespace std;

int main(){

int choice = 0;

while (true){

cout << "Input integers plaese. " <<endl;

cin >> choice;

if ( cin.good() == 1 ){

// 如果輸入的資料型別匹配原先定義的資料型別

cout << "Good! " <<endl;

break;

}

else{

cout << "Input error! Input again! " << endl ;

cin.clear();// 重新整理、清除緩衝區中先前所輸入的資訊

cin.get();//相當於"getchar()"或"cin.ignore(1024,'\n')"。

//當這裡使用cin.ignore()【注意函式中不賦任何引數】時,跟cin.get()效果完全一致;

//而使用帶引數的cin.ignore(1024, ’\n’)時則會忽略輸入的相應位元組。

} } }

四二、getch()、getchar()函式

  1. getch():
    getch是一個計算機函式,在windows平臺下從控制檯無回顯地取一個字元,在linux下是有回顯的。用法是int
    getch(void)。這個函式可以讓使用者按下任意鍵而不需要回車就可以接受到使用者的輸入。可以用來作為"press
    any key to continue"的實現。

  2. getchar():getchar是讀入函式的一種。它從標準輸入裡讀取下一個字元,相當於getc(stdin)。返回型別為int型,為使用者輸入的ASCII碼或EOF。該函式宣告在stdio.h標頭檔案中,使用的時候要包含stdio.h標頭檔案。getchar由巨集實現:#define
    getchar()
    getc(stdin)。getchar有一個int型的返回值。當程式呼叫getchar時.程式就等著使用者按鍵。使用者輸入的字元被存放在鍵盤緩衝區中。直到使用者按回車為止(回車字元也放在緩衝區中)。當用戶鍵入回車之後,getchar才開始從stdio流中每次讀入一個字元。getchar函式的返回值是使用者輸入的字元的ASCII碼,若檔案結尾(End-Of-File)則返回-1(EOF),且將使用者輸入的字元回顯到螢幕。如使用者在按回車之前輸入了不止一個字元,其他字元會保留在鍵盤快取區中,等待後續getchar呼叫讀取。也就是說,後續的getchar呼叫不會等待使用者按鍵,而直接讀取緩衝區中的字元,直到緩衝區中的字元讀完後,才等待使用者按鍵。

四三、C++字型和背景帶顏色的輸出的方法

  1. 設定初始終端輸出的視窗和字型顏色並在全域性中使用: system(“color
    02”);。其中,color後面的數字表示顏色的值。

  2. 輸出情況下的字型顏色使用:

【格式】printf("\033[字背景顏色;字型顏色m 字串 \033[0m" );

【例子】printf("\033[1m\033[45;33m HELLO_WORLD \033[0m\n");

【顏色程式碼】

字背景顏色範圍: 40--49 字型顏色: 30—39

40:黑 30:黑

41:紅 31:紅

42:綠 32:綠

43:黃 33:黃

44:藍 34:藍

45:紫 35:紫

46:深綠 36:深綠

47:白色 37:白色

【ANSI控制碼】

\033[0m關閉所有屬性

\033[1m設定高亮度

\033[4m下劃線

\033[5m閃爍

\033[7m反顯

\033[8m消隱

\033[30m--\033[37m設定前景色

\033[40m--\033[47m設定背景色

\033[nA游標上移n行

\03[nB游標下移n行

\033[nC游標右移n行

\033[nD游標左移n行

四四、二叉樹的遍歷、拷貝和葉子節點計數

# include <iostream>

# include <cstring>

# include <cstdlib>

# include <conio.h>

# include <stack>

using namespace std;

typedef struct BiTNode{

int data;

struct BiTNode *lchild, *rchild;

}BiTNode;

void preOrder(BiTNode *root){

if( root == NULL ){

return;

}

//Traverse root firstly and then the left child and right child

cout << root->data; //Traverse the root

preOrder(root->lchild);//Traverse the left child

preOrder(root->rchild);//Travese the right child

}

void inOrder(BiTNode *root){

if( root == NULL ){

return;

}

//Traverse left child firstly and then the root and right child

inOrder(root->lchild);//Traverse the left child

cout << root->data; //Traverse the root

inOrder(root->rchild);//Travese the right child

}

void postOrder(BiTNode *root){

if( root == NULL ){

return;

}

//Traverse the left child then right child and then the root lastly

postOrder(root->lchild);//Traverse the left child

postOrder(root->rchild);//Travese the right child

cout << root->data; //Traverse the root

}

//Not recomended!

// int sum;

// void countLeaf(BiTNode *T){

// if( T != NULL){

// if( T -> rchild == NULL && T -> lchild == NULL ){

// sum++;

// }

// if( T -> lchild ){

// countLeaf(T -> lchild);

// }

// if( T -> rchild ){

// countLeaf(T -> rchild);

// }

// }

// }

void countLeaf(BiTNode *T, int *sum){

if( T != NULL){

//#################

if( T -> rchild == NULL && T -> lchild == NULL ){

// Must be careful that '*sum ++' means changing the adress

//while '(*sum) ++' means changing only the number of that adress

(*sum)++;

}

// This can be placed anywhere among three "if's" as the recursion of traversion

if( T -> lchild ){

countLeaf(T -> lchild, sum);

}

if( T -> rchild ){

countLeaf(T -> rchild, sum);

}

}

}

BiTNode * CopyTree(BiTNode *T){

BiTNode *newNode = NULL;

BiTNode *newRc = NULL;

BiTNode *newLc = NULL;

if( T == NULL ){

return NULL;

}

if( T -> lchild != NULL){

newLc = CopyTree(T -> lchild);

}else{

newLc = NULL;

}

if( T -> rchild != NULL){

newRc = CopyTree(T -> rchild);

}else{

newRc = NULL;

}

newNode = (BiTNode *) malloc (sizeof(BiTNode));

if(newNode == NULL){

return NULL;

}

newNode -> lchild = newLc;

newNode -> rchild = newRc;

newNode -> data = T -> data;

return newNode;

}

//Go down to the left to find the root

BiTNode * goLeft(BiTNode *T, stack<BiTNode *> &s){

if (T == NULL)

{

return NULL;

}

// Observe whether T has left child, push it into the stack while return T if it
doesn't.

while(T->lchild != NULL)

{

s.push(T);

T = T->lchild;

}

return T;

}

void inOrderStack(BiTNode *T){

BiTNode *t = NULL;

stack<BiTNode *> s;

t = goLeft(T, s);

while (t)

{

cout << t->data;

if( t -> rchild != NULL ){

t = goLeft(t->rchild, s); // Found the start point of the right child

}

else if(!s.empty()){ // If it doesn't have the right child, turn back according
to the stack's top

t = s.top();

s.pop();

}else{ // If the tree t doesn't have the right child and the stack is empty

t = NULL;

}

}

}

int main(){

int choice = 0;

char c;

while (true){

cout << "Chooese the traversal type: " << endl;

cout << "1. Recurion type " <<endl;

cout << "2. Stack type " <<endl;

cout << "3. Exit \n" <<endl;

cin >> choice;

if ( cin.good() == 1 ){ // If the input type is correspondant with the defined
type

BiTNode t1, t2, t3, t4, t5;

//Must set numbers in order not to cause null pointer

memset(&t1, 0 , sizeof(BiTNode));

memset(&t2, 0 , sizeof(BiTNode));

memset(&t3, 0 , sizeof(BiTNode));

memset(&t4, 0 , sizeof(BiTNode));

memset(&t5, 0 , sizeof(BiTNode));

t1.data = 1;

t2.data = 2;

t3.data = 3;

t4.data = 4;

t5.data = 5;

t1.lchild = &t2;

t1.rchild = &t3;

t2.lchild = &t4;

t3.lchild = &t5;

if (choice == 1){

system("cls"); // Clean to refresh the screen

cout << "Recursion type shown below." << endl;

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

//sum = 0;

//countLeaf(&t1);

//cout << "The number of the leaves in this tree is : " << sum << endl;

// Make global varible sum in private space

{

int sum = 0;

countLeaf(&t1, &sum);

cout << "The number of the leaves in this tree is : " << sum << endl;

}

cout << "The preOrder of the tree is : " ;

preOrder(&t1);

cout << "\nThe inOredr of the tree is : " ;

inOrder(&t1);

cout << "\nThe postOrder of the tree is : " ;

postOrder(&t1);

cout << "\n";

{

BiTNode *root = CopyTree(&t1);

cout << "The tree after copied is : " ;

preOrder(root);

}

cout << "\nEnd! " << endl;

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

break;

}

else if(choice == 2){

system("cls"); // Clean to refresh the screen

cout << "Stack type shown below." << endl;

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

cout << "The inOredr of the tree in stack type is : " ;

inOrderStack(&t1);

cout << "\nEnd! " << endl;

// $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

break;

}

else if ( choice ==3){

exit(0);// Equals to 'return 0';]

}else{

system("cls"); // Clean to refresh the screen

cout << "\033[1m\033[40;31mOut of choice! Please input the choices
below!\033[0m\n" << endl;

}

}else{

system("cls");

// Set the color of the text

cout << "\033[1m\033[40;31mInput error! Please input in correct
form!\033[0m\n" << endl ;

cin.clear(); // Clear the buffer

cin.ignore(1024,'\n'); // Get the pressed key "enter". Similiar to "cin.get();"
or "getchar()"

}

}

}

.

四五、各類演算法的時間複雜度

排序演算法學習連結推薦

https://www.bilibili.com/video/av9830014/?spm_id_from=333.788.videocard.0

十一、選擇排序演算法

# include <iostream>

using namespace std;

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

cout << " " << array[i];

}

cout << endl;

}

void swap(int array[],int i, int j) {

int temp = array[i];

array[i] = array[j];

array[j] = temp;

}

void SelectionSort(int array[], int len) {

int i = 0;

int j = 0;

int k = -1;

for( i = 0 ; i < len ; i++) {

k = i; // 尋找最小元素的下標

for( int j = i + 1 ; j < len ; j++) {

if( array[j] < array[k]) { // 開始尋找最小元素的下標

k = j;

}

}

swap(array, i, k); // 交換兩個數字

}

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99};

int len = sizeof(array)/sizeof(*array);

cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

SelectionSort(array, len);

cout << "The numbers after sorted are : " ;

printArray(array, len);

return 0;

}

四六、插入排序演算法

# include <iostream>

using namespace std;

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

cout << " " << array[i];

}

cout << endl;

}

void InsertionSort(int array[], int len) {

int i = 0;

int j = 0;

int k = -1;

int temp = -1;

for( i = 1 ; i < len ; i++) {

k = i; // 待插入的位置

temp = array[k];

for( int j = i - 1 ; (j >= 0) && (array[j] > temp) ; j--) {

array[j+1] = array[j]; // 元素後移

k = j; // k需要插入的位置

}

array[k] = temp; // 元素插入

}

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99};

int len = sizeof(array)/sizeof(*array);

cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

InsertionSort(array, len);

cout << "The numbers after sorted are : " ;

printArray(array, len);

return 0;

}

四七、氣泡排序演算法

# include <iostream>

using namespace std;

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

cout << " " << array[i];

}

cout << endl;

}

void swap(int array[],int i, int j) {

int temp = array[i];

array[i] = array[j];

array[j] = temp;

}

void BubleSort(int array[], int len) {

int i = 0, j = 0, exchange = 1; //
Exchange用作標記是否已經排好序的標記器,便於優化

for( i = 0 ; (i < len) && exchange ; i++) {

exchange = 0; // 表明已經排好序

for( int j = len-1 ; j > i ; j--) {

if( array[j] < array[j-1]) {

swap(array, j, j-1); // 交換兩個數字

exchange = 1; // 如果上面一行swap程式碼被執行表示還沒有排好序

}

}

}

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99};

int len = sizeof(array)/sizeof(*array);

cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

BubleSort(array, len);

cout << "The numbers after sorted are : " ;

printArray(array, len);

return 0;

}

四八、希爾排序演算法

# include <iostream>

using namespace std;

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

cout << " " << array[i];

}

cout << endl;

}

void ShellSort(int array[], int len) {

int i = 0;

int j = 1;

int temp =0;

int k = - -1;

int gap = len;

do{

// 分組

gap = gap/3 + 1; //
業界實驗後得到的數值,當其為3的時候收斂於1,情況最好,當然也可以不是3。

for( i = gap ; i < len ; i += gap){

k = i;

temp = array[k];

for( j = i - gap ; (j >= 0) && (array[j] > temp); j -= gap ){

array[j+gap] = array[j];

k = j;

}

array[k] = temp;

}

}while(gap>1);

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99};

int len = sizeof(array)/sizeof(*array);

cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

ShellSort(array, len);

cout << "The numbers after sorted are : " ;

printArray(array, len);

return 0;

}

四九、快速排序演算法

#include<iostream>

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

std::cout << " " << array[i];

}

std :: cout << std :: endl;

}

void QuickSort(int iArray[],int left, int right) {

//快速排序之前先判斷一下當前待排序陣列元素個數是不是大於1 否則就沒有必要排序

if (left >= right) {

//直接退出排序程式碼 沒有必要進行排序了

return;

}

//開始進行快排演算法

//首先我們先儲存left索引對應的資料 當前資料作為切割陣列的軸

int piovt = iArray[left];

//定義臨時變數儲存陣列2端的索引

int leftIndex = left;

int rightIndex = right;

while (leftIndex < rightIndex) {

//現在我們通過迴圈從右邊開始搜尋一個比軸值小的資料

while (leftIndex < rightIndex) {

//如果右邊的數大於當前的引數軸值

if (piovt <= iArray[rightIndex]) {

//右端索引指示器左移

rightIndex--;

} else {

//說明我們右端出現比軸值更大的資料

//這個時候我們就可以把這個更大的資料填充到索引軸索引對應的地方

iArray[leftIndex] = iArray[rightIndex];

leftIndex++;

//我們需要跳出迴圈了當前工作完畢

break;

}

}

//從左邊開始搜尋一個比軸值更大的數填寫上次留下的坑

while (leftIndex < rightIndex) {

//如果左邊的資料小於軸值 我們索引指示器就往右走

if (piovt >= iArray[leftIndex]) {

leftIndex++;

} else {

//說明我們在左端找到了比軸值更大的資料

iArray[rightIndex] = iArray[leftIndex];

rightIndex--;

break;

}

}

}

iArray[leftIndex] = piovt;

QuickSort(iArray, left, leftIndex - 1);

QuickSort(iArray, rightIndex + 1, right);

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99};

int len = sizeof(array)/sizeof(*array);

std :: cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

QuickSort(array, 0, len-1);

std :: cout << "The numbers after sorted are : " ;

printArray(array, len);

return 0;

}

五十、歸併排序演算法

#include <iostream>

using namespace std;

void printArray(int array[], int len) {

for( int i = 0 ; i < len ; i++ ) {

cout << " " << array[i];

}

cout << endl;

}

// lefCount = number of elements in L

// rightCount = number of elements in R.

void Merge(int *array,int *L,int leftCount,int *R,int rightCount) {

// i - to mark the index of left aubarray (L)

// j - to mark the index of right sub-raay (R)

// k - to mark the index of merged subarray (A)

int i =0, j = 0, k = 0;

while(i<leftCount && j< rightCount) {

if(L[i] < R[j]) array[k++] = L[i++];

else array[k++] = R[j++];

}

while(i < leftCount) array[k++] = L[i++];

while(j < rightCount) array[k++] = R[j++];

}

// Recursive function to sort an array of integers.

void MergeSort(int *array,int n) {

int mid,i, *L, *R;

if(n < 2) return; // Base condition. If the array has less than two element, do
nothing.

mid = n/2; // Find the mid index.

// Create left and right subarrays

// Mid elements (from index 0 till mid-1) should be part of left sub-array

// And (n-mid) elements (from mid to n-1) will be part of right sub-array

L = new int[mid];

R = new int [n - mid];

for(i = 0; i<mid; i++) L[i] = array[i]; // Creating left subarray

for(i = mid; i<n; i++) R[i-mid] = array[i]; // Creating right subarray

MergeSort(L,mid); // Sorting the left subarray

MergeSort(R,n-mid); // Sorting the right subarray

Merge(array,L,mid,R,n-mid); // Merging L and R into A as sorted list.

// The delete operations is very important

delete [] R;

delete [] L;

}

int main() {

int array[] = {12, 5, 45, 65, 76, 74, 13, 2, 5, 7, 99}; // Creating an array of
integers.

int len = sizeof(array)/sizeof(*array);

cout << "The numbers waiting to be sorted are : " ;

printArray(array, len);

// Calling merge sort to sort the array.

MergeSort(array,len);

cout << "The numbers after sorted are : " ;

// Printing all elements in the array once its sorted.

printArray(array, len);

return 0;

}

五一、用C++求和

方式一:

# include <iostream>

using namespace std;

void Sum() {

int array[1024];

int sum = 0;

//int len = sizeof(array[])/sizeof(*atrray[]);

cout << "Please enter some of the integers and press 0 to end: " << endl;

for (int i = 0; i < 10; i++) {

cin >> array[i];

if(cin.good()) {

sum += array[i];

if(array[i] == 0) {

break;

}

} else {

cout << "Please input the intigers as reqiured above: " <<endl;

cin.clear();

cin.ignore(1024,'\n');

}

}

cout << "The sum of the numbers you have entered is : " << sum << endl;

}

int main () {

Sum();

}

方式二:

#include <iostream>

using namespace std;

int ORIGINALmain() {

int i;

cout<<"請輸入一串整數和任意數目的空格:";

int sum = 0;

//一直到輸入的值是整數的時候進入while迴圈,當輸入字元時,scanf返回0,什麼都不讀,然後再次呼叫scanf

while (cin>>i) {

sum += i;

while (cin.peek() == ' ') {

//遮蔽空格,peek()函式:從輸入流中讀取一個字元 但該字元並未從輸入流中刪除

cin.get();//從指定的輸入流中提取一個字元(包括空白字元),

}

if (cin.peek()== '\n') {

break;

}

}

cout<<"結果是:"<<sum<<endl;

system("pause");

return 0;

}

int main() {

int i = 0, sum = 0;

cout<<"Please enter some integers : ";

while (true) {

cin >> i;

if(cin.good() == 1) {

sum += i;

while (cin.peek() == ' ') {

//遮蔽空格,peek()函式:從輸入流中讀取一個字元 但該字元並未從輸入流中刪除

cin.get();//從指定的輸入流中提取一個字元(包括空白字元),

}

if (cin.peek()== '\n') {

break;

}

} else {

system("cls");

cout << "\033[1m\033[40;31mPlease enter the integers as required!\033[0m"
<< endl;

cin.clear();

cin.ignore(1024,'\n');

}

}

cout << "The sum of the numbers you have entered is:" << sum << endl;

return 0;

}

五二、巧用Visual Studio

  1. F12是跳轉到定義的快捷鍵,Ctrl + - 是向後導航的快捷鍵,按下即可返回。

  2. 顯示屬性視窗。

  3. F12,轉到定義。

  4. Shift+Tab,取消製表符。

  5. F5,執行除錯; Ctrl + F5,執行不除錯;Shift+F5,結束除錯。

  6. Ctrl+E+C,註釋選中內容;Ctrl+E+U,取消註釋內容。

  7. Ctrl+W+X,開啟工具箱。

  8. Ctrl+E+W,自動換行。

  9. Ctrl+M+M,隱藏或展開當前巢狀的摺疊狀態。

  10. Ctrl+L,刪除一行內容。

  11. Ctrl+E+D,排版整個文件格式。

  12. F11,逐語句除錯;F10,逐過程除錯;F9,啟用/停止斷點;ctrl+shift+F9,刪除全部斷點。

  13. Ctrl+E+S,檢視空白。

  14. Ctrl+Alt+L,開啟解決方案資源管理器。

  15. F1,顯示MSDN幫助。

  16. Shift+Alt+F10,匯入名稱空間。

  17. Ctrl+F4,關閉當前標籤視窗。

  18. Ctrl+Shift+空格鍵,檢視引數資訊。

  19. Shift+Alt+C,新增類。

  20. Ctrl+R+E,宣告屬性後,快捷鍵生成屬性的get和set方法。

  21. 游標快速切換到下一行:Ctrl + Shift + Enter;

  22. 游標快速切換到上一行:Ctrl + Enter。

五三、Android程式設計

  1. 儲存使用者輸入的內容到一個檔案中:

private void save(String inputText) {

FileOutputStream outputStream = null;

BufferedWriter bufferedWriter = null;

try {

outputStream = openFileOutput("SavedContent", Context.MODE_PRIVATE);

bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));

bufferedWriter.write(inputText);

} catch (IOException e) {

e.printStackTrace();

}finally {

if (bufferedWriter != null){

try {

bufferedWriter.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

  1. 將使用者儲存的內容在回退的時候讀取出來而不是重新整理之後就清空處理:

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

editText = (EditText) findViewById(R.id.edit);

String inputText = load();

if (!TextUtils.isEmpty(inputText)){

editText.setText(inputText);

editText.setSelection(inputText.length());

Toast.makeText(this,"Restored succeed!",Toast.LENGTH_LONG).show();

}

}

public String load(){

FileInputStream in = null;

BufferedReader reader = null;

StringBuilder content = new StringBuilder();

try {

in = openFileInput("SavedContent");

reader = new BufferedReader(new InputStreamReader(in));

String line = ""; // Must assign the line read at the first time

while ((line = reader.readLine()) != null){

content.append(line); // Must be the 'line' instead of the function
'reader.readLine()'

}

} catch (IOException e) {

e.printStackTrace();

}finally {

if (reader != null){

try {

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

return content.toString(); }

五四、視訊合成與特效

  1. J、K:時間線快速定位到前一個關鍵幀或後一個關鍵幀;

  2. B和N:快速確定預覽區起始和結束區範圍, 快速在選定的範圍內進行預覽;

  3. +和-(字幕上的按鍵):放大縮小時間線預覽區;

  4. PageUp和PageDown:時間線想前一幀或後一幀移動。

  5. Shift+PageUp和PageDown:時間線向前一幀或後一幀移動,每點選一次表示移動10幀

  6. 五大屬性快捷鍵:錨點——A;位置——P鍵;縮放——S鍵;旋轉——R鍵;透明度——T鍵。

  7. U: 展開當前圖層所有做了關鍵幀的屬性。

  8. M(一次):單獨排程調出遮罩路徑屬性;(兩次)——調出遮罩所有屬性。

  9. F:單獨調出遮罩羽化屬性。

  10. F3:檢視當前圖層效果面板。

  11. 選擇啟用當前圖層並選擇Mask矩形遮罩時,會以圖層為基礎新增遮罩,矩形框外部則不可見。

  12. 不選中當前圖層時則會從新繪製一個Mask;

  13. 畫完遮罩後雙擊邊緣線——矩形框整體被選中,可以只移動矩形框;

  14. 畫完遮罩後單擊邊緣線——矩形框中的那條線被選中,可以移動那條線;

  15. 畫完遮罩之後選擇G——鋼筆工具,點選到的部位會產生變化,可以改變其形狀;

  16. 按住Alt後可以對鋼筆工具劃出的線條頂點單個進行移動。

  17. 【------------------------------製作進度條--------------------------------】

    1. 橢圓形狀——雙擊
    1. 變成正圓
    1. Ctrl+D複製一個新的
    1. 將新圓縮小
    1. 選擇小圓描邊(Stroke)屬性並新增虛線,調整虛線(Dashes)數目至合適值
    1. Ctrl+D複製整個形狀圖層

    選擇第一個原先的圖層並在效果與預設面板中查詢“填充(Fill)”效果,並新增到該圖層上。

    1. 改變新增上效果之後的第一圖層填充色到合適的值。
    1. 新建物件,命名為Counter,為此圖層新增“滑塊控制(Slider Control)”效果。
    1. 開啟第二個圖層併為其新增“修建路徑(Trim Path)”動畫。

    進入修建路徑並在“結束”選項中按住Alt並單擊,點選渦輪並將其拖至“滑塊控制”效果中。

    1. 為第二層加“梯度漸變(Gradient Ramp)”效果並設定其開始顏色和結束顏色。
    1. Ctrl+Y新建一個純色層設定其顏色為黑色並放置在所有圖層最底下。
  18. 雙擊文字工具新建一個文字圖層,按住Alt鍵選擇文字圖層的“源文字(Source
    Text)”並輸入表示式:

  19. 【中文版本】

  20. beginCount =thisComp.layer("Counter").effect("滑塊控制")("滑塊");

  21. stopCount = thisComp.layer("Counter").effect("滑塊控制")("滑塊");

  22. beginTime = 0; // start counting at time = 0

  23. countDur = 5; // count for 5 seconds

  24. Math.round(linear(time,beginTime,beginTime + countDur,beginCount,stopCount))

    • "%"
  25. 【英文版本】

  26. beginCount =thisComp.layer("Counter").effect("Slider Control")("Slider");

  27. stopCount = thisComp.layer("Counter").effect("Slider Control")("Slider");

  28. beginTime = 0; // start counting at time = 0

  29. countDur = 5; // count for 5 seconds

  30. Math.round(linear(time,beginTime,beginTime + countDur,beginCount,stopCount))

    • "%"
    1. 選中Counter圖層併為其“滑塊控制”效果新增幀動畫,設定起始點和結束點。

    為文字圖層新增位置幀動畫以適當改變文字位置使得不同位數的數字能夠劇中顯示。

    1. 按F9將入點設定為緩入。
  31. 【------------------------------製作進度條--------------------------------】

  32. 使用LOOKS可以高質量調色。

五五、消隱演算法

一、消隱

當我們觀察空間任何一個不透明的物體時,只能看到該物體朝向我們的那些表面,其餘的表面由於被物體所遮擋我們看不到。

若把可見的和不可見的線都畫出來,對視覺會造成多義性。

會有後邊兩種情況

要消除二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱作消除隱藏線和隱藏面,簡稱為消隱。

消隱不僅與消隱物件有關,還與觀察者的位置有關。

二、消隱的分類

1>按消隱物件分類

線消隱:消隱物件是物體的邊

面消隱:消隱物件是物體上的面

2>按消隱空間分類

物體空間的消隱演算法:

以場景中的物體為處理單位。假設場景中有k個物體,將其中一個物體與其餘k-1個物體逐一比較,僅顯示它可見表面已達到消隱的目的。(此類演算法通常用於線框圖的消隱!)

影象空間的消隱演算法:

以螢幕視窗內的每個畫素為處理單元。對螢幕上每個畫素進行判斷,決定哪個多邊形在該畫素可見(這類演算法是消隱演算法的主流)

三、影象空間的消隱演算法:

1>Z-buffer演算法

2>掃描線演算法

3>Warnock消隱演算法

畫家演算法:去除隱藏面最簡單的演算法

原理:若場景中有許多物體,就是先畫遠的物體,再畫近的物體。這樣一來,近的物體自然就會蓋住遠的物體。

但實際情況並不理想,在三維場景中,一個物體可能有些部分遠,有些部分近,所以不管用什麼順序畫,都無法得到正確的結果,所以畫家演算法只能解決簡單場景的消隱問題。

Z-buffer演算法

1、也稱Z緩衝區演算法和深度緩衝器演算法(能跟蹤螢幕上每個畫素深度的演算法),讓計算機生成複雜圖形成為可能。

2、該演算法有幀緩衝器和深度緩衝器,對應兩個陣列:

Intensity(x,y)-屬性陣列(幀緩衝器),儲存影象空間每個可見畫素的光強或顏色

Depth(x,y)-深度陣列(Z-buffer),存放影象空間每個可見畫素的Z座標。

Z-buffer儲存的是經過投影變換後的z座標,距離眼睛近的地方z座標的解析度比較大,遠處的解析度小。

3、Z-buffer演算法思想

(開一個和幀快取一樣大小的儲存空間,利用空間上的犧牲換區演算法上的簡潔)

(1)先將z緩衝器中各單元的初始值置為最小值

(2)當要改變某個畫素的顏色值時,首先檢查當前多邊形的深度值是否大於該畫素原來的深度值

(3)如果大於原來的z值,說明當前多邊形更靠近觀察點,用它的顏色替換畫素原來的顏色。

4、虛擬碼

Z-buffer演算法(){

幀快取全置為背景色

深度快取全置為最小z值(比如賦一個10^-8次方)

For(每一個多邊形){

掃描轉換該多邊形

For(該多邊形所覆蓋的每個畫素(x,y)){

計算該多邊形在該畫素的深度值Z(x,y);

If(Z(x,y)大於Z快取在(x,y)的值){

把Z(x,y)存入Z快取中(x,y)處

把多邊形在(x,y)處的顏色值存入幀快取的(x,y)處

}}}}

5、優點:

(1)演算法簡單直觀

(2)在畫素級上以近物取代遠物。與物體在螢幕上的出現順序是無關緊要的,有利於硬體實現

(3)記憶體容量不再是問題後很受歡迎

6、缺點

(1)佔空間大(因為要開一個和幀緩衝器一樣大的陣列,多了z快取)

(2)沒有利用圖形的相關性和連續性(提高演算法的效率要利用圖形的相關性和連續性)

(3)是在畫素級上的消隱演算法

Z-buffer演算法的改進(只用一個深度快取變數zb的改進演算法)

1、將快取陣列zb改為一個深度快取變數zb

2、虛擬碼

Z-buffer演算法(){

幀快取全置為背景色

For(螢幕上的每個畫素(i,j)){

深度快取變數zb置最小值MinValue

For(多面體上的每個多邊形Pk){

If(畫素點(i,j)在Pk的投影多邊形之內){

計算Pk在(i,j)處的深度值depth;

If(depth>zb){

Zb=depth;

Index=k;(記錄多邊形的序號)}}}

If(zb!=MinValue)

計算多邊形Pindex在交點(i,j)處的光照顏色並顯示

}}

關鍵問題:判斷畫素點(i,j)在Pk的投影多邊形之內不容易

深度如何求?多邊形的平面方程為ax+by+cz+d=0,可得出z值

點與多邊形的包含性檢測

一、射線法

1、由被測點P處向y=-無窮方向作射線

2、交點個數是奇數,則被測點在多邊形內部,交點個數是偶數,則被測點在多邊形外部

3、若射線正好經過多邊形的頂點,則採用“左開右閉”的原則來實現

即:當射線與某邊的頂點相交時,若邊在射線的左側,交點有效,計數;若邊在射線的右側,交點無效,不計數;

4、用射線法來判斷一個點是否在多邊形內的弊端:

(1)計算量大(因為要大量求交)

(2)不穩定(左開右閉有誤差,在左邊但由於誤差算在了右邊,不計數了)

二、弧長法

以P點為圓心作單位圓,把邊投影到單位圓上,對應一段段弧長,規定逆時針為正,順時針為負,計算弧長代數和

代數和為0,點在多邊形外部;代數和為2π,點在多邊形內部;代數和為π,點在多邊形邊上

演算法為什麼穩定?即使算出來後代數和不為0,而是0.1或0.2,那麼基本可以斷定這個點在外部,可以認為是有計算誤差引起的。

但是算弧長並不容易,因此派生出一個新的方法-

以頂點符號為基礎的弧長累加方法

1、不計算角度,用一個規定取代原先的計算

3、同一個象限認為是0,跨過一個象限是π/2,跨過兩個象限是π。這樣當要計算代數和的時候,就不用投影了,只要根據點所在的象限一下子就判斷出多少度,這樣幾乎沒什麼計算量,只有一些簡單的判斷,效率高。

區間掃描線演算法

1、該演算法放棄了z-buffer演算法,是一個新的演算法,這個演算法被認為是消隱演算法中最快的之一,因為不管是哪一種z-buffer演算法,都是在畫素級上處理問題,每個畫素都要進行判斷,甚至一個畫素要進行多次(一個畫素可能會被多個多邊形覆蓋)

2、

3、主要思想:如果把掃描線和多邊形的交點求出來,對每個區間,只要判斷畫素畫什麼顏色,那麼整個區間的顏色都解決了(單位是區間)

4、如何確定小區間的顏色?

(1)小區間上沒有任何多邊形,如[a4,a5],用背景色顯示

(2)小區間上只有一個多邊形,如[a1,a2],顯示該多邊形的顏色

(3)小區間上存在兩個或兩個以上的多邊形,如[a6,a7],必須通過深度測試判斷哪個多邊形可見

Warnock消隱演算法

1、思想:採用分而治之的思想,利用了堆疊的資料結構(把物體投影到全螢幕視窗上,然後遞迴分割視窗,直到視窗內目標足夠簡單,可以顯示為止)

2、什麼情況,畫面足夠簡單可以立即顯示?

(1)視窗中僅包含一個多邊形

(2)視窗與一個多邊形相交,且視窗內無其它多邊形

(3)視窗被多邊形包圍

(4)視窗與一個多邊形分離(視窗顯示背景色)

3、如何判別一個多邊形和視窗是分離的?

4、如何判別一個多邊形在視窗內?

5、演算法步驟:

(1)如果視窗內沒有物體則按背景色顯示

(2)如果視窗內只有一個面,則把該面顯示出來

(3)否則,視窗內含有兩個以上的面,則把視窗等分成四個子視窗。對每個小視窗再做上述同樣的處理。這樣反覆的進行下去。

光柵掃描演算法小結

1、直線段的掃描轉換演算法

(1)DDA演算法主要利用了直線的斜截式方程(y=kx+b),在這個演算法裡引用了增量的思想,結果把一個乘法變成了一個加法。

(2)中點法是採用的直線的一般式方程,也採用了增量的思想,比DDA演算法的優點是採用了整數加法

(3)Bresenham演算法也採用了增量和整數加法,優點是這個演算法還能用於其它二次曲線

2、多邊形的掃描轉換和區域填充

把邊界表示的多邊形轉換成由畫素點表示的多邊形

有四個步驟:求交、排序、配對、填色。為了避免求交運算,引入了一個新的思想-圖形的連貫性。手段就是利用增量演算法和特殊的資料結構,兩個指標陣列和兩個指標連結串列。

3、直線和多邊形裁剪

Cohen-Suther land演算法和Liang-barsky演算法

Cohen-Suther
land核心為編碼,把螢幕分成9個部分,用4個編碼來描述這9個區域,通過4位編碼的“與”“或”運算來判斷直線段是否在視窗內或外。

Liang-barsky演算法:

用引數方程表示

把被裁剪的直線段看成是一條有方向的邊,把視窗的四條邊分成兩類:入邊和出邊

4、走樣、反走樣

用離散量表示連續量,有限的表示無限的會導致一些失真,這種現象成為走樣。

反走樣主要有三種方法:
提高解析度、區域取樣、加權區域取樣

提高解析度有物理限制,因為解析度不能無限增加

區域取樣可以把關鍵部位變得模糊一點,有顏色的過渡區域,產生好的視覺效果

加權區域時不但要考慮區域取樣,而且要考慮不同區域的權重,用積分、濾波等技巧來做。

5、消隱

在繪製場景是消除被遮擋的不可見的線或面,稱作消除隱藏線和隱藏面,簡稱為消隱。

按消隱空間分類:

(1)物體空間以場景中的物體為處理單元

(2)影象空間以螢幕視窗內的每個畫素為處理單元

區間掃描線演算法:發現掃描線和多邊形的交點把掃描線分成若干區間,每個區間只有一個多邊形可以顯示。利用這個特點可以把逐點處理變成逐段處理,提高了演算法效率。

Warnock消隱演算法:採用了分而治之的思想,利用了堆疊的資料結構

核心思想:

1>增量

2>編碼

3>整數、符號判別

4>圖形連貫性

5>分而治之:把一個複雜物件進行分塊,分到足夠簡單再進行處理

五五、PhotoShop基礎

  1. 如何繪製虛線:

(方法1)用鋼筆點兩個錨點就能得到一條是形狀圖層的直線,在描邊裡設定為虛線,高階設定中可以選擇虛線的寬度和間距。再配合自由變換,你就有任意虛線了。這種辦法還可以畫中間帶點的虛線,也可以畫彎曲的虛線。這樣做出來的虛線還是向量的,可以自由縮放,不會失真。

https://pic4.zhimg.com/80/v2-78b0f4a4c92e841444a4e2fec39d3735_hd.jpg

*(方法2)*在選單欄,視窗按鈕下開啟畫筆選項面板,在畫筆面板中可對畫筆進行任意的設定,如間距,設定時可在下方實時預覽間距大小。  

https://pic4.zhimg.com/80/v2-79846a856c865568083b6ca3158b5e2a_hd.jpg

【但是這樣會有一定的侷限性,如果畫筆中沒有方角畫筆就無法按照此方式繪製出方角虛線,新建畫筆預設請見下方講解。】

  1. 新建畫筆:

  2. 選中已經柵格化好的圖層中某一需要為其設定畫筆的圖形或圖案;

  3. 對該圖形建立選區並選中該圖形的選區;

  4. 選擇選單欄中的“編輯”——“自定義畫筆預設”[快捷鍵Alt+E+B]即可將該圖形作為畫筆儲存起來。

五六、安卓程式設計

  1. Switch控制元件的用法:

private Switch mSwitch;

private TextView mText;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mSwitch = (Switch) findViewById(R.id.switch_);

mText = (TextView) findViewById(R.id.text_);

// 新增監聽

mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
{

@Override

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

if (isChecked){

mText.setText("開啟");

}else {

mText.setText("關閉");

}

}

});

}

  1. 震動:

Vibrator vibrator = (Vibrator)this.getSystemService(this.VIBRATOR_SERVICE);

long[] patter = {1000, 1000, 2000, 50};

vibrator.vibrate(patter, 0);

最後一行中vibrate的第二引數表示從哪裡開始迴圈,比如這裡的0表示這個陣列在第一次迴圈完之後會從下標0開始迴圈到最後,這裡的如果是-1表示不迴圈。

vibrator.cancel();可以取消震動。

AudioAttributes#USAGE_ALARM