计算机入门笔记

版本控制

  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. 方法一思路:通过对时间的计算,每隔一段时间让物体旋转,实现来回移动。

float TranslateSpeed = 0.02f;

float TranslateSpeedTime = 0.1f;

void Update () {

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 onm ouseDown()

{

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

}

函数可以获取鼠标事件,在里面加入相应的触发事件。

当获取鼠标离开某个区域时需要用到 public onm ouseExit(){}函数。
在使用这些函数的时候必须要指定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 onm ouseEnter()

{

audio.PlayOneShot(dangerMusic);

}

void onm ouseExit()

{

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 function Atan (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. 旋转坐标转换的矩阵推导

两角和(差)公式

计算机入门笔记

旋转变换一般是按照某个圆心点,以一定半径 旋转一定的角度α。假定点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

上一篇:Unity 固定渲染管线 SetTexture实例


下一篇:2021-01-24